fix(agent): keep MCP connection failures from cancelling gateway - #288
Closed
guix4ever wants to merge 208 commits into
Closed
fix(agent): keep MCP connection failures from cancelling gateway#288guix4ever wants to merge 208 commits into
guix4ever wants to merge 208 commits into
Conversation
Replace the hardcoded, stale commit-scope list in CLAUDE.md section 3.3 with a pointer to the README Repo layout section, and finish the public-phase wording: drop the internal-AI-stats framing and the GitLab-era JIRA / merge-request residue from the trailer rules, the PR-open flow, and the PR template. Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
…e_and_template_cleanup docs: point commit-scope at README and finish public-phase cleanup
…adius Prompt injection can't be fully prevented, so this hardens defense-in-depth: label untrusted content with a tamper-resistant boundary the model is told to treat as data, and shrink the blast radius of a successful injection. Scope follows the analysis report — high-value, low-complexity items; deeper items (sandbox default flip, capability tokens, MCP/skill signing, memory audit log) are deliberately deferred. - security/trust.py: wrap_untrusted() fences external content with a per-call random nonce, so embedded fake close-markers can't escape the fence. - system prompt: a guideline telling the model to treat fenced/external content as data, watch for "ignore the above" / "you are now" style directives, and confirm high-impact actions via ask_user. - fence untrusted tool output at the single chokepoint (ContextBuilder.add_tool_result) and inside the subagent's own loop, plus the subagent result re-injected into the main agent. - fence recalled memory and the sentinel's memory/attention context as unverified; the planner is told unverified content must not drive privileged actions (spawn / nudge). - web_fetch: use security.network.validate_url_target (DNS-resolving SSRF check) instead of the previous scheme-only validator. - DirectExecutor: pass only an env allowlist (no host secrets) rather than the full host environment; warn loudly when running unsandboxed (backend=none). - subagents: per-session rolling-hour spawn rate limit (auto-recovering) to stop a prompt-injected re-injection loop without locking out legitimate use. Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
…-turns switch Wire the TUI /model command end to end: model.* RPC handlers (options / save_key / disconnect / add_model / remove_model); a config.set "model" branch that rebuilds the live provider and reassigns the agent loop so a switch takes effect on the next turn (guarded by is_turn_active); ProviderConfig.models for the manual picker catalogue; the custom provider routed through LiteLLM; and a base LLMProvider.chat_stream non-streaming fallback so non-litellm providers degrade gracefully instead of crashing. Frontend: the model picker gains an api_base input, inline model add/delete, and a structured switch through config.set; openrpc-derived types regenerated. Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
feat: add TUI /model — provider/model config CRUD and between-turns model switch
…ning_untrusted_content feat(security): label untrusted content and contain injection blast radius
Two related changes to the EverOS-backed memory subsystem. Migrated three recall improvements from the legacy everclaw repo (env vars renamed EVERCLAW_* -> RAVEN_*): - drop current-session hits from the # Memory recall so it does not duplicate the live conversation already in the history window (over-fetch top_k*2, then filter by session_id) - add `raven agent --message-file` for prompts beyond the shell ARG_MAX - env-gated episodic recall + preference annotation in the consolidator (RAVEN_EPISODIC_TOPK / RAVEN_ANNOTATE_PREFS / RAVEN_EPISODIC_CLUSTER), default-off Restructured when EverOS is invoked so native memory owns short-term and EverOS owns long-term: - recall is now on-miss: native runs every turn; EverOS user-track recall fires only when native surfaces nothing relevant to the query - the per-turn EverOS store is removed; EverOS consolidation runs only (a) intra-day when a session crosses everos_consolidation_threshold_pct of the context window (default 80, background, non-blocking) and (b) nightly at everos_nightly_consolidation_hour (default 0, offline) - backend.store gains force_flush to promote a batch immediately; consolidation is incremental per session (metadata watermark) so the two triggers never double-ingest - native short-term compaction (maybe_consolidate_by_tokens) is unchanged Tests: unit coverage for the threshold / nightly / on-miss paths plus a full in-memory lifecycle smoke, and a real-everos e2e for the force_flush single-store recall path. Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
EverOS defaulted both its user-level config toml and its data root (sqlite / lancedb / .index / ome.db) to a bare ~/.everos. Scope raven's instance under ~/.everos/raven so config and data stay isolated from any other EverOS consumer on the machine. Add update_everos.configure_everos_env(): it sets EVEROS_CONFIG_FILE and EVEROS_MEMORY__ROOT via setdefault (so an explicit operator override still wins) before EverOS's @cache-d load_settings() first runs. The everos backend factory and the understand_media tool factory call it as the earliest raven-controlled EverOS entry points; _EVEROS_CONFIG now derives from a single _EVEROS_BASE constant. No data migration: existing ~/.everos data is left untouched and a fresh ~/.everos/raven is created lazily on first write/use. Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
A fresh onboard now materializes the user-facing subset of the memory / plugins / skillForge extension blocks into ~/.raven/config.json so the knobs are discoverable and editable without reading the source. Seeding is setdefault-based (non-clobbering) and runs on every onboard, so it also backfills a pre-existing config that predates these blocks. - memory.backend defaults to "everos" (schema default); _memory_enabled now gates on an llm model being present, so a fresh modelless seed is treated as "not yet enabled" and the skip / non-interactive guard still resolves it to None when memory is opted out or left unconfigured. - skillForge.everos.enabled is seeded on; router.hub.endpoint is seeded to the shared Skill Hub URL with apiKey left null for the user to fill. - plugins.config["everos-memory"] is seeded with identity wiring that mirrors memory.userId / agentId so recall identities match. - internal-infra fields (embedding / reranker endpoints + their API keys, mass_library_db) are NOT written, so a real Bearer token never lands in a plaintext config; they stay at their schema defaults. Co-authored-by: Claude (claude-opus-4-8) <noreply@anthropic.com>
…lt_extension_blocks Feat/onboard default extension blocks
…recall_improvements feat: native short-term memory + everos long-term consolidation
…_memory_recall revert: pr EverMind-AI#9 native short-term memory + everos long-term consolidation
…readme_ci chore: prepare open source launch
## Summary - switch project metadata and license text to Apache-2.0 while retaining upstream notices - add public-ready community files: issue templates, SECURITY, CONTRIBUTING, CODE_OF_CONDUCT, Dependabot, and a stricter PR template - align README license copy with the public launch posture ## Repository settings updated via GitHub API - squash-only merge enabled; merge commit and rebase merge disabled - delete branch on merge and update branch enabled - Discussions enabled; Projects and Wiki disabled - Actions default workflow permissions set to read-only and PR self-approval disabled - description and homepage updated for Raven ## Verification - npm ci --ignore-scripts --no-audit --no-fund (root) - npm ci --ignore-scripts --no-audit --no-fund (ui-tui) - npm ci --ignore-scripts --no-audit --no-fund (bridge) - npx --no-install commitlint --from origin/main --to HEAD --config commitlint.config.cjs - PYTHONPATH=. python3 scripts/check_commit_messages.py origin/main..HEAD - uv run pre-commit run --from-ref origin/main --to-ref HEAD - git diff --check origin/main...HEAD ## Notes - GitHub branch protection/rulesets returned 403 while the repository is private on the current plan. Enable required checks after making the repository public. - Auto-merge remained disabled after the API patch, likely due repository/org availability or policy.
## Summary - add lightweight overview files for top-level repository sections that still displayed old non-conventional commit messages - update existing benchmark and demo READMEs with public-facing guidance - make the GitHub directory listing show a clean conventional commit message after merge without rewriting main history ## Verification - uv run pre-commit run --files LICENSES/README.md benchmarks/README.md demos/README.md docs/README.md raven/README.md tests/README.md - npx --no-install commitlint --from origin/main --to HEAD --config commitlint.config.cjs - PYTHONPATH=. python3 scripts/check_commit_messages.py origin/main..HEAD - uv run pre-commit run --from-ref origin/main --to-ref HEAD ## Note A normal pull request cannot rename historical commits already merged into main. This PR instead updates the affected directories with useful public-facing overview docs so GitHub displays the new conventional commit as the latest directory change.
Bumps [python-socketio](https://github.com/miguelgrinberg/python-socketio) from 5.16.1 to 5.16.2. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/miguelgrinberg/python-socketio/releases">python-socketio's releases</a>.</em></p> <blockquote> <h2>Release 5.16.2</h2> <p>See <a href="https://github.com/miguelgrinberg/python-socketio/blob/main/CHANGES.md">CHANGES.md</a> for release notes.</p> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/miguelgrinberg/python-socketio/blob/main/CHANGES.md">python-socketio's changelog</a>.</em></p> <blockquote> <h1>python-socketio change log</h1> <p><strong>Release 5.16.3</strong> - 2026-06-15</p> <ul> <li>Catch all exceptions in redis and rabbitmq client managers <a href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1581">#1581</a> (<a href="https://github.com/miguelgrinberg/python-socketio/commit/80bb5c9b07e6b53e45f610ec29a24124c539d41b">commit</a>)</li> </ul> <p><strong>Release 5.16.2</strong> - 2026-05-21</p> <ul> <li>Prevent unnecessary resource allocations <a href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1574">#1574</a> (<a href="https://github.com/miguelgrinberg/python-socketio/commit/ca140fe44d0ceb3004073645222abec182d8784b">commit</a>)</li> <li>Add zizmor to CI builds <a href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1570">#1570</a> (<a href="https://github.com/miguelgrinberg/python-socketio/commit/664dc27ec6f34179ed1724430eda3520627fc642">commit</a>)</li> </ul> <p><strong>Release 5.16.1</strong> - 2026-02-06</p> <ul> <li>Use configured JSON module in managers <a href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1549">#1549</a> (<a href="https://github.com/miguelgrinberg/python-socketio/commit/6229261ae6e8c01e675097242e333ee84587a544">commit</a>)</li> <li>Admin UI fixes: remove duplicate tasks, report transport upgrades (<a href="https://github.com/miguelgrinberg/python-socketio/commit/1c2eab13a92fac9e43663eb0b5f099eb1c40ea5b">commit</a>)</li> <li>Switch to Furo documentation template (<a href="https://github.com/miguelgrinberg/python-socketio/commit/add47d8c7abca697a2804141bbf29bfb095f7d5e">commit</a>)</li> <li>Add Python free-threading to CI <a href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1554">#1554</a> (<a href="https://github.com/miguelgrinberg/python-socketio/commit/ccdd2004a038ae4b8171a05120c5d0787332f7ee">commit</a>)</li> </ul> <p><strong>Release 5.16.0</strong> - 2025-12-24</p> <ul> <li>Address deprecation warnings (<a href="https://github.com/miguelgrinberg/python-socketio/commit/b235699d9b06564753c570b76055997e9d62a938">commit</a>)</li> <li>Drop Python 3.8 and 3.9 from CI builds (<a href="https://github.com/miguelgrinberg/python-socketio/commit/d0728d2f74538762dd551fa9cd0cd1fd5aedfa37">commit</a>)</li> </ul> <p><strong>Release 5.15.1</strong> - 2025-12-16</p> <ul> <li>Restore support multiple arguments via pubsub emits <a href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1540">#1540</a> (<a href="https://github.com/miguelgrinberg/python-socketio/commit/c279f26bb8c9887c4ca99d4d81ad331c4844438c">commit</a>)</li> </ul> <p><strong>Release 5.15.0</strong> - 2025-11-22</p> <ul> <li>Retry initial Redis connection <a href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1536">#1534</a> ([commit <a href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1">#1</a>](<a href="https://github.com/miguelgrinberg/python-socketio/commit/1e903e173a2d7b04599c4f7f9630c1abbb531fad">https://github.com/miguelgrinberg/python-socketio/commit/1e903e173a2d7b04599c4f7f9630c1abbb531fad</a>) [commit <a href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/2">#2</a>](<a href="https://github.com/miguelgrinberg/python-socketio/commit/5e898a9b93526e6e667767e54c60f4c84589989d">https://github.com/miguelgrinberg/python-socketio/commit/5e898a9b93526e6e667767e54c60f4c84589989d</a>))</li> <li>Correctly regenerate RabbitMQ binding after a connection failure <a href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1516">#1516</a> (<a href="https://github.com/miguelgrinberg/python-socketio/commit/c52e93b4a328d98a968bfbdec0cfd598b73ee913">commit</a>) (thanks <strong>Gritty_dev</strong>!)</li> <li>Support <code>ext_type</code> in the <code>MsgPackPacket</code> class <a href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1521">#1521</a> (<a href="https://github.com/miguelgrinberg/python-socketio/commit/208925344a48485d2cd56e40eb74266c3bcb5311">commit</a>)</li> <li>Support sending <code>bytesarray</code>s when using pub/sub managers (<a href="https://github.com/miguelgrinberg/python-socketio/commit/6c9b9974f72e2efdf62407ecab24ee6995448098">commit</a>)</li> <li>Fix typos in documentation <a href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1520">#1520</a> (<a href="https://github.com/miguelgrinberg/python-socketio/commit/db3f1c2a0105c30cb833ddfca8f05fe4320468fd">commit</a>) (thanks <strong>Lê Nam Khánh</strong>!)</li> <li>Improvements to the logging documentation (<a href="https://github.com/miguelgrinberg/python-socketio/commit/b423d0e38eef559b7e81acb7e32059de305f982c">commit</a>)</li> </ul> <p><strong>Release 5.14.3</strong> - 2025-10-29</p> <ul> <li>Support Python's native <code>ConnectionRefusedError</code> exception to reject a connection <a href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1515">#1515</a> (<a href="https://github.com/miguelgrinberg/python-socketio/commit/f3b18bde3f16437b223491d4c3e440ea37105fe3">commit</a>)</li> <li>Push binary data to the aiopika client manager <a href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1514">#1514</a> (<a href="https://github.com/miguelgrinberg/python-socketio/commit/194e1b7f277b5f72e1de78d3f614e7b8b6c788ac">commit</a>)</li> </ul> <p><strong>Release 5.14.2</strong> - 2025-10-15</p> <ul> <li>Restore binary message support in message queue setups <a href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1509">#1509</a> (<a href="https://github.com/miguelgrinberg/python-socketio/commit/bab4a10f48aaae11d7f832ebe5c30ad3f85d31b3">commit</a>)</li> <li>Fix formatting of client connection error <a href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1507">#1507</a> (<a href="https://github.com/miguelgrinberg/python-socketio/commit/f298c9b54d76ab09ff72935937e1b9575bc45ffd">commit</a>)</li> <li>Add 3.14 and pypy-3.11 CI tasks (<a href="https://github.com/miguelgrinberg/python-socketio/commit/1f4cd3b025c294f25208ec3c05b5f8df6209e403">commit</a>)</li> <li>Improve documentation of the <code>BaseManager.get_participants()</code> method (<a href="https://github.com/miguelgrinberg/python-socketio/commit/33722a0d96036f005188b07b8b46a5ef091fe65f">commit</a>)</li> </ul> <p><strong>Release 5.14.1</strong> - 2025-10-02</p> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/miguelgrinberg/python-socketio/commit/6e2b7175c97c2785edd2ea60df026c12240c785c"><code>6e2b717</code></a> Release 5.16.2</li> <li><a href="https://github.com/miguelgrinberg/python-socketio/commit/cb658291085842372a16863d83e14373f85b3d3e"><code>cb65829</code></a> update python-engineio version</li> <li><a href="https://github.com/miguelgrinberg/python-socketio/commit/ca140fe44d0ceb3004073645222abec182d8784b"><code>ca140fe</code></a> prevent unnecessary resource allocation (<a href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1574">#1574</a>)</li> <li><a href="https://github.com/miguelgrinberg/python-socketio/commit/b29beef6bfd38d5a3c3b89da96b7a4574a91efcf"><code>b29beef</code></a> tox configuration</li> <li><a href="https://github.com/miguelgrinberg/python-socketio/commit/e8981302a3d5b525ee73c2ec9c13859ce2f764e8"><code>e898130</code></a> Bump ujson from 5.4.0 to 5.12.1 in /examples/server/sanic (<a href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1573">#1573</a>) #nolog</li> <li><a href="https://github.com/miguelgrinberg/python-socketio/commit/05c32f5da85fe5e5d647c937c7eab0972760ca05"><code>05c32f5</code></a> Bump qs and body-parser in /examples/server/javascript (<a href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1572">#1572</a>) #nolog</li> <li><a href="https://github.com/miguelgrinberg/python-socketio/commit/287dc6715b801d319f5baa0fb727e232e728b8aa"><code>287dc67</code></a> Bump qs and body-parser in /examples/client/javascript (<a href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1571">#1571</a>) #nolog</li> <li><a href="https://github.com/miguelgrinberg/python-socketio/commit/664dc27ec6f34179ed1724430eda3520627fc642"><code>664dc27</code></a> add zizmor to ci (<a href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1570">#1570</a>)</li> <li><a href="https://github.com/miguelgrinberg/python-socketio/commit/14c62366a85cfe34e0bd69a22b905eb329873dd3"><code>14c6236</code></a> Bump django in /examples/server/wsgi/django_socketio (<a href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1566">#1566</a>) #nolog</li> <li><a href="https://github.com/miguelgrinberg/python-socketio/commit/29b2e5cdbe87e8216bc46fccb864b84ab9f55cf9"><code>29b2e5c</code></a> Bump aiohttp from 3.13.3 to 3.13.4 in /examples/server/aiohttp (<a href="https://redirect.github.com/miguelgrinberg/python-socketio/issues/1565">#1565</a>) #nolog</li> <li>Additional commits viewable in <a href="https://github.com/miguelgrinberg/python-socketio/compare/v5.16.1...v5.16.2">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/EverMind-AI/Raven/network/alerts). </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
## Summary
Upgrades the pinned EverOS from `1.2.1` to `1.2.3`, per the SOP in
`docs/memory-plugin-architecture.md` section 7.2. Four packages move:
`everos`, `everalgo-agent-memory` `0.3.1` to `0.4.0`,
`everalgo-user-memory` `0.3.2` to `0.4.0`, and `lancedb` `0.33.0` to
`0.34.0`.
**The adapter needs no change.** Every EverOS symbol it imports still
exists
with an identical signature (12 call sites checked), and
`memory/search/dto.py`
plus the `search` / `memorize` / `get` route modules are byte-identical
between
the two releases, so the HTTP contract is untouched.
**No data migration.** No LanceDB table schema changed: `user_profile`
and
`knowledge_topic` are byte-identical between the two releases, and the
five
files that differ at all (`episode`, `atomic_fact`, `agent_case`,
`agent_skill`, `foresight`) differ only in docstrings. So the
column-type
startup check that `1.2.2` added does not fire on an index this upgrade
produces. `lancedb` `0.33` and `0.34` were verified interchangeable in
both
directions - each writes Lance file format `v2.1`, and a table written
by
either opens, searches over the other's IVF and FTS indexes, upserts and
prunes under the other - so reverting the pin can still read the index.
Three things reviewers should know, all recorded in the new SOP section
7.3
rather than only here:
1. **`extract_foresight` now ships disabled.** The default moved in
code, not
in `default_ome.toml`, so an existing `~/.everos/ome.toml` does not opt
out
of the change. Any deployment relying on foresight entries must set
`enabled = true` per install.
2. **Agent-skill extraction works for the first time.** Before `1.2.3` a
cascade race meant it produced zero `SKILL.md` files, so
`EverosSkillSource` has been contributing nothing; it now contributes
real
skills to the prompt.
3. **The Linux install floor moves to glibc 2.28.** `lancedb 0.34.0`
ships no
`manylinux_2_17` wheel and no sdist, and `install.sh` hands the exported
lockfile to the user's `uv` as constraints, so CentOS or RHEL 7,
Ubuntu 18.04 and Amazon Linux 2 fail at install time rather than falling
back to a source build.
Two smaller changes are consequences, not choices. `1.2.3` gives
`extract_agent_skill` a module-level `_reader` singleton beside
`_writer`, and
the integration conftest reset only the writers it knew about. And the
bundled
plugin manifest version moves to `1.2.0`, which two tests assert as a
literal;
SOP step 5 now names both so the next bump does not rediscover them.
## Type
- [ ] Fix
- [ ] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [x] Other
Dependency upgrade. `chore` has no box of its own.
## Verification
```
uv run pytest tests/test_everos_plugin_discovery.py tests/test_everos_backend.py \
tests/test_everos_http_adapter.py tests/test_memory_backend_protocol.py \
tests/test_memory_backend_contract.py -q
=> 158 passed
uv run pytest <all unit files that mention everos> \
tests/test_memory_backend_protocol.py tests/test_memory_backend_contract.py \
-q -p no:randomly
=> 1336 passed, 30 skipped
uv run ruff check <the four changed .py files> => All checks passed
uv run ruff format --check <the four changed .py files> => 4 files already formatted
make check-large-files => pass
```
`make lint-python` on this branch only covers the nine CI-tooling files
in
`PYTHON_LINT_TARGETS`, so ruff was run directly against the files this
change
touches instead of relying on that target.
The 30 skips are parametrized cases in
`test_provider_resolution_invariants.py` ("not a gateway" and similar);
they
are structural, unrelated to EverOS, and skip identically on the
baseline.
Beyond the suites above, the upgrade was exercised end to end through
Raven's
own entrypoints on an isolated `RAVEN_HOME`: `raven plugins` reports the
new
manifest version, `raven doctor` still parses the capability matrix from
a
`/health` that `1.2.3` extended with a `cascade` block, and a real
`raven agent -m` turn recalls memories that `1.2.1` wrote - including
after
rolling the pin back, with the index having been written by `lancedb
0.34` in
between. The `extract_foresight` change is visible in the OME
`run_record`
table: every other strategy advances by one dispatch per turn while
`extract_foresight` stops being dispatched at all.
**The `real_llm` layer of SOP step 3 could not be validated, and this PR
does
not claim it passes.** All three tests in
`tests/integration/test_everos_backend_e2e.py` fail with
`no such table: md_change_state`, and they fail identically on the
baseline,
checked by re-syncing to the old pin and running the same command. The
cause is
pre-existing and independent of the upgrade: `everos_env` isolates the
test by
monkeypatching `EVEROS_ROOT` to a tmp dir (`conftest.py:126`), but the
backend's start path calls `configure_everos_env()`, which deliberately
assigns
rather than defaults that variable (`update_everos.py:228`), so the
spawned
server serves the recorded root while the test's drain helper reads the
tmp
root's sqlite. Worth fixing, but not here.
- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [x] User-facing docs or screenshots are updated when needed
## Risk
Two user-visible behaviour changes, both upstream defaults rather than
choices
made here: foresight extraction stops running unless an install opts in,
and
agent-skill extraction starts running for the first time, which changes
prompt
content and token usage. The `ome.db` `run_record` ring buffer for
`skill_cluster_updated` also grows from roughly 0.8 KB to 14 KB per row
because
`SkillClusterUpdated` now carries a 1024-dim embedding, about 14 MB
instead of
0.8 MB at the default 1000-record cap.
Rollback is `git revert` of this commit plus `uv sync`. The index does
not need
restoring: the schemas are unchanged and `lancedb` `0.33` reads what
`0.34`
wrote, both verified. Installs on glibc below 2.28 are the one case a
revert is
required rather than optional.
- [x] Security impact considered
- [x] Backward compatibility considered
- [x] Rollback path is clear for risky changes
## Related Issues
N/A
Co-authored-by: zhanghui <23442919+gloryfromca@users.noreply.github.com>
Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
…verMind-AI#350) ## Summary An identity EverOS cannot accept becomes a directory segment on its write path, so `EverosBackend.start()` rejects it. The rejection carried the one message a user can act on -- it names the key and the accepted pattern -- and no surface delivered it as one. Three of the four call sites fold the raise into `logger.exception`; under the TUI loguru is file-only, so there an invalid identity turned memory off with nothing on screen. The fourth, in `import_commands._build_and_run`, is not wrapped at all, so the `ValueError` escaped as a bare traceback. Where it was visible it was a traceback, which is not a thing a user reads as "edit this key". The state machine then told the user the opposite of the truth. `start()` died before assigning a state, so `_state` stayed `UNKNOWN`, which is not in `_NEVER_HAD_MEMORY`; every `store()` after it counted a dropped write, and the session ended with: ``` 1 turn(s) were not written to long-term memory because the memory service was unavailable. ``` The service was fine. That sends the user to debug a server that never broke. **The change.** Catch the `ValueError` in `start()`, give it a `BAD_IDENTITY` state, and file that state with the other two the user must fix themselves: - terminal, so a stray probe cannot promote it to `READY`; - never-had-memory, so no dropped-write count and no outage wording; - the message goes to stderr, matching what the unconfigured-LLM and missing-binary paths beside it already do. Two things came out of running it rather than reading it: 1. **The message needs escaping.** It quotes the accepted-character class, and rich reads `[a-zA-Z0-9_.@+-]` as a markup tag and eats it, leaving the user the pattern `^+$` to match their id against. The test now pins the full class. 2. **`raven import` had the same defect on its own gate.** It kept the outage wording and pointed at the server log, which holds nothing about a config error. It now names the config keys instead. Before / after, same config (`memory.userId: "bad/id"`), same command: ``` - 1 turn(s) were not written to long-term memory because the memory service was - unavailable. + Long-term memory is off: memory.userId='bad/id' is not accepted by EverOS: it + becomes a directory segment on the write path, so it must match + ^[a-zA-Z0-9_.@+-]+$ and must not be '.' or '..'. + Fix memory.userId / memory.agentId in your config.json, then start a new session. ``` ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification Re-run after rebasing onto `d35e398c`, the current `main`. ``` uv run pytest tests/test_everos_backend.py tests/test_cli_import_commands.py \ tests/test_everos_plugin_discovery.py -q 162 passed uv run pytest -q 1 failed, 6478 passed, 43 skipped, 13 deselected uv run ruff check <the four touched files> All checks passed uv run ruff format --check <the four touched files> 4 files already formatted ``` The one failure is `tests/test_cli_theme.py::test_bold_accent_renders_styled_not_bare`. It is pre-existing on this base: checked out `d35e398c` in a clean worktree and ran `uv run pytest tests/test_cli_theme.py` there, same failure, 1 failed / 44 passed. It passes in isolation and fails when the file runs as a whole, so it is order-dependent, and it is unrelated to anything here. That file is untouched by this change. Manual check, sandbox `RAVEN_HOME`, `memory.userId: "bad/id"`, `raven agent -m "hi"`: prints the block quoted above, and the session no longer ends with the dropped-write line. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk Terminal-only wording plus one new enum member. No config, schema, or protocol change; an identity that was accepted before is accepted now, on the same code path. The only behaviour change for a valid setup is none. For an invalid identity the turn behaves as it did -- memory off, turn completes -- and only what the user is told changes. Rollback is reverting the single commit. `ServiceState.BAD_IDENTITY` is new. Every consumer was checked: `raven/cli/import_commands.py` (handled here) and `tests/test_everos_plugin_discovery.py` (reads `READY` only). `_may_spawn` allowlists `UNKNOWN` via `_SPAWNABLE_STATES`, so a new member is non-spawnable without a change. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues Fixes EverMind-AI#271 Co-authored-by: zhanghui <23442919+gloryfromca@users.noreply.github.com> Co-authored-by: Claude (claude-opus-5[1m]) <noreply@anthropic.com>
…imit (EverMind-AI#348) ## Summary `SkillHubClient.search` put the caller's query into the `q` parameter verbatim. The deployed hub is fronted by a load balancer (`server: awselb/2.0`) that answers `403 Forbidden` -- not `414` -- once a request's query string passes 2048 bytes, and `q` carries a retrieval query that can be as long as a user's whole message. So a long prompt did not search on a prefix; it lost hub discovery outright. The failure is quiet: `SkillForgeRouter._safe_search` turns a source error into an empty candidate list, so the only symptom is one warning line and skills silently never being found. Non-ASCII made the real ceiling far lower than it looks. One CJK character costs 9 bytes encoded (three UTF-8 bytes, three `%XX` characters each), so the budget was about 226 characters of Chinese -- under which essentially every non-trivial task ran with no hub candidates at all. The fix trims `q` to the longest prefix that fits, measured in encoded width rather than character count, and sizes its budget from what the other params leave over rather than assuming `q` is alone. It also logs what was dropped, so a trimmed query does not read as a whole one. Measured boundary against the deployed hub: a 2048-byte query string is served, 2049 is refused. Two decisions worth naming, so the next reader does not re-derive them: - The cap lives in the client, not the caller. The limit is a property of this endpoint's wire contract, and the client is the one place every caller passes through. - Trimming is per character, not a slice of the encoded form. Slicing the encoding would cut a `%XX` escape in half and produce invalid UTF-8. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification Reproduced against the deployed hub before the fix, bisecting the boundary: a query string of 2048 bytes returns 200, 2049 returns 403, with `server: awselb/2.0` on the refusal. In CJK terms, 226 characters pass (2045 bytes) and 227 fail (2054 bytes). End-to-end through the real client, on the prompt shape that produced the original 403 (an 860-character mixed Chinese/ASCII query): ``` DEBUG raven.skill_hub.client: hub search query trimmed to fit the 2048-byte query-string cap (366 of 860 characters kept) RESULT: HTTP 200, 10 items returned ``` The same query untrimmed is a 4951-byte query string and returns 403. ``` uv run pytest tests/test_skill_hub_source.py tests/test_skill_hub_client.py \ tests/test_skill_hub_tools.py tests/test_skill_hub_policy.py \ tests/test_skill_segment_builder.py -q -> 97 passed ``` Full suite, and the three new tests proved not to be theatre by reverting only the client change and watching them go red: ``` uv run pytest -q -p no:randomly -> 72 failed, 6572 passed, 36 skipped, 13 deselected # same command on the base commit, failing test IDs diffed: -> 72 failed; identical set. 0 introduced, 0 fixed by accident. # revert client.py only, keep the tests: -> 3 failed (the 3 new ones), 2 passed ``` The 72 failures are pre-existing on the base commit and unrelated to this change (`test_tui_rpc_session.py`, `test_cli_cron_commands.py`, `test_cli_import_commands.py`, `test_cli_onboard_commands.py`, and four single-test files). ``` uv run ruff check raven/skill_hub/client.py tests/test_skill_hub_source.py -> All checks passed! uv run ruff format --check raven/skill_hub/client.py tests/test_skill_hub_source.py -> 2 files already formatted ``` - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed Disclosed gaps: `make lint` also runs `lint-tui`, which could not run here because `eslint` is not installed in this checkout; this change is Python-only and touches no TypeScript. `mypy` is not part of this project's dev dependencies, so no type check beyond ruff was available. No docs changed: nothing in `docs/` stated a length contract for this parameter, so no text went stale. ## Risk Behaviour change: a query whose encoded form exceeds the budget is now truncated instead of being sent whole. Before this change such a request was refused outright and the candidate list came back empty, so a truncated query is strictly more useful than the status quo -- there is no case that previously worked and now returns less. Queries inside the budget are byte-identical to before, which the pre-existing envelope test pins. The parameter order in the emitted URL changes (`q` now serialises after `category` and `sort`) because the budget is computed from what the other params consume. Query parameters are order-independent on this endpoint. Rollback is reverting the commit; there is no migration, no persisted state, and no config surface involved. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
…ind-AI#352) ## Summary LiteLLM fetches its price and context table over the network at import unless `LITELLM_LOCAL_MODEL_COST_MAP` is set beforehand. Nothing in this repo sets it, so the unit suite reads a remote file as a fixed input. A `deepseek/deepseek-v4-pro` row appeared upstream and turned 19 tests red on `main`, on bytes no commit here had touched, minutes after the same bytes had passed. `token_rates` and `resolve_context_window` consult LiteLLM's table first and only then the OpenRouter catalogue, which the autouse `_no_openrouter_network` fixture already keeps off the wire for exactly this reason. Several tests mock an OpenRouter catalogue and arrange for the LiteLLM tier to miss, so the mocked number is the one under test. When the new row started answering that lookup first, the mock was never reached: the counting transport recorded 0 calls, no disk cache was written, and the assertions read live figures instead (a window of 1048576 where the mock said 163840, a cost of 0.0033 where the mock said 0.00125). The fixture's promise to keep the suite off the network covered one of the two doors. The change is three small parts: - `conftest` publishes `LITELLM_LOCAL_MODEL_COST_MAP` at module scope, before any test module imports LiteLLM. Setting it before first *use* is too late, because the remote table is loaded at import. `setdefault`, so a run can still be pointed at the live table deliberately. - Two assertions genuinely needed a row that exists only remotely. `minimax/MiniMax-M3` is the one MiniMax row absent from the bundled table, which does carry the other direct `minimax/` models. Both now pin the row, so what they exercise is the resolution ladder rather than what MiniMax published this morning. The key is `minimax/` and not `minimax-global/` because `_candidates` maps the plan-billed prefix onto the direct one before the table is consulted. - A third test asserts the offline guarantee itself. Nothing else in the suite would notice it being lost, which is how it was lost in the first place. Checked and deliberately not changed: - **`_litellm_price_table` and `_try_litellm_context_window` describe that table as "static" and "offline"** (four places in `raven/providers/rates.py`). Both are wrong in production, where the flag is never set and the import does reach the network, and they are the reason the tests were written believing it. Correcting them means touching the provider hot path, and whether production should read a pinned table instead of a fetched one is a behaviour question with its own trade-offs: a bundled table is reproducible but goes stale, and it lacks rows the fetched one has, which is the same gap the MiniMax pin works around. Left for a change that can weigh it properly, because `main` is red right now and this is the smallest surface that clears it. - **The `allow_fetch=False` tier is still order-dependent.** `_try_litellm_context_window` returns early only while LiteLLM is absent from `sys.modules`, so whether that tier is shut depends on which test imported LiteLLM first. Pinning the table makes those cases deterministic, which is what this change is for, but it does not make the contract they assert true. ## Type - [ ] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [x] Other -- tests only ## Verification Full suite on the base commit and on this branch, same machine, failing test IDs diffed rather than counts compared: ``` base (0e544ec): 100 failed, 6379 passed, 46 skipped, 13 deselected branch: 81 failed, 6399 passed, 46 skipped, 13 deselected introduced: 0 fixed: 19 (exactly the set that went red on main) ``` The two files that exercise the ladder, with no flag on the command line, which is what proves the `conftest` placement is early enough: ``` pytest tests/test_provider_rates.py tests/test_agent_loop_usage_sink.py -q -> 71 passed ``` Not vacuous: the same two files with `LITELLM_LOCAL_MODEL_COST_MAP=False`, which pre-empts the `setdefault` and so removes the guarantee: ``` -> 20 failed, 51 passed ``` Twenty are the nineteen original failures plus the new test that exists to notice exactly this. Repo gate and lint: ``` python scripts/check_commit_messages.py 0e544ec..HEAD -> exit 0 ruff check raven tests scripts -> All checks passed! ruff format --check raven tests scripts -> 844 files already formatted ``` - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed Disclosed gaps. The 81 failures that remain are present on the base commit too and are unrelated to this change (58 in `test_tui_rpc_session.py`, the rest across the CLI cron, import, onboard and TUI command suites); they are environment-dependent on this machine and CI does not reproduce them, which is why the red count on CI was 19 and not 100. The full suite segfaults at interpreter finalization on base and branch alike, before and after. `make lint` also runs `lint-tui` and `lint-bridge`, which could not run here because their Node toolchains are not installed in this checkout; this change adds no TypeScript. No docs changed: nothing outside `tests/` mentions this flag, which is the same grep that confirms production never sets it. ## Risk Test-only. No file under `raven/` changes, so no runtime behaviour moves: in production LiteLLM still fetches its table exactly as before, and this only stops the suite from asserting against it. The trade-off is that the suite now reads a table pinned to whatever LiteLLM bundles, so a genuine upstream correction to a window or a price no longer reaches these assertions. That is the intended direction -- a unit test should fail because the code changed -- but it means the numbers in them describe LiteLLM's bundled data rather than a live vendor claim, and a LiteLLM upgrade can still move them. Two existing tests changed meaning and are worth a reviewer's eye: `test_a_plan_billed_provider_still_reports_a_window` and `test_the_window_those_families_report_is_the_vendors_own` previously passed by reading a live row, and now pass by reading a pinned one. Rollback is reverting the commit; the suite returns to reading the live table, with the failure mode described above. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues\n\nFixes EverMind-AI#358 Co-authored-by: Claude (claude-opus-5[1m]) <noreply@anthropic.com>
…rMind-AI#355) ## Summary A colleague reported the TUI composer feeling laggy while typing; it did not reproduce on a fast local terminal with short English input. Two causes, and they amplify each other. **Typing was being read as a paste.** Typing and pasting are the same bytes on stdin, so the parser merged any multi-character run into one nameless keypress and the composer treated it as an unbracketed paste held behind a 50ms debounce. SSH, tmux and a blocked event loop all coalesce keystrokes, so continuous typing kept resetting that debounce and the input box stalled for the whole burst. A CJK input method commits several characters at once, so it took this path on almost every word. The fix stops guessing from length. A plain byte run is split into one keypress per code point, and bracketed paste (DEC 2004) becomes the paste signal it was always meant to be. App asks the terminal with DECRQM whether the mode actually took, riding the query batch that already carries XTVERSION, and the parser records the answer in its own state. Confirmed: every unmarked run is typing. Not confirmed: only runs that could not be a paste line - free of control bytes and no longer than a typing burst (32 characters) - split, so multi-line paste still stays whole on terminals without the markers. Three input bugs shared that root cause and go away with it: an auto-repeated backspace inserted literal DEL characters instead of deleting, a return arriving in the same read as the text before it was swallowed without sending, and a control key arriving alongside text inserted a literal control character. **Wide characters could never take the fast-echo path.** Fast echo writes a character straight to the terminal and defers the React update to the next frame, so a keystroke that takes it costs no render. Its guard required display width to equal string length, which a CJK character or an emoji can never satisfy, so every wide character forced a full tree reconcile. The terminal advances two cells for a wide character on its own, so the guard now only holds what the terminal cannot: a single grapheme, at the end of a single line that still has room. The two halves ship together on purpose. Without the second, a two-character IME commit would trade one delayed render for two immediate ones. Also memoizes the `useVirtualHistory` return value. `useMainApp` feeds it into the `appTranscript` memo, so a fresh object identity on every render re-rendered the whole transcript on every keystroke - the amplifier that made long sessions worse. Note for review: this is the first change to the ink fork's implementation in the repository's visible history (four earlier commits touched only its README, export surface and lockfile). Its README asks for the same test and review bar as first-party code, so `parse-keypress.ts` is the part worth reading closely. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification Run from `ui-tui/`, on this branch rebased onto `main` at 0e544ec, after a clean `npm ci`: - `npm test` - 90 files, 1031 tests passed, 0 failed - `npm run type-check` - clean - `npm run lint` - 0 errors, 22 warnings, identical to the pre-change baseline (compared warning-by-warning against a clean checkout of `main`; only line numbers moved) Three load-bearing new tests were mutation-checked: the implementation was broken on purpose and each test was confirmed to fail, then restored. One of them silently passed on the first attempt because component tests load the bundled `packages/hermes-ink/dist` rather than its source; it was re-checked after rebuilding. Manual, in `raven tui --dev` after rebuilding the ink bundle: holding backspace deletes instead of inserting `^?`; typing a sentence and pressing return immediately sends it; CJK input keeps up with typing; a several-hundred-line paste still collapses into a single placeholder; a dropped file path is still recognised. New coverage: | File | Tests | Covers | | --- | --- | --- | | `parse-keypress.test.ts` | 28 -> 47 | split rules in both modes, five DECRQM reply statuses, paste content untouched | | `textInputFastAppend.test.ts` | 10 | wide characters and emoji accepted; line end, newline, column limit, multi-grapheme and zero-width rejected | | `textInputTypingBurst.test.tsx` | 2 | end to end, a coalesced run from real stdin through the parser into the composer | | `virtualHistoryIdentity.test.tsx` | 2 | identity survives an unrelated re-render, changes when the item list does | Three existing tests in `parse-keypress.test.ts` were updated. They asserted that a text run arrives as a single key, which is the shape this change replaces; they now assert the intent instead - every event is a key and their sequences join back to the original text. That also checks each event's kind, which the old form did not. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk User-visible behaviour changes, all in the composer: - typing no longer stalls behind the paste debounce; - CJK and emoji no longer force a full re-render per character; - backspace auto-repeat, return-after-typing and control-key-with-text now do what they say instead of inserting literal control characters. The regression surface is paste. On a terminal that confirms DEC 2004 nothing about paste handling changes: markers still delimit it and the parser already reassembled marked pastes across reads. On a terminal that does not confirm it, short printable runs now arrive as typing, which is why the 32-character threshold is there - it keeps long single-line pastes whole so placeholder collapsing and dropped-path detection still work. A paste of a short single-line string on such a terminal is the one case that changes: it inserts character by character instead of going through the paste handler. Rollback is a revert of the two commits; they touch no state, no protocol and no persisted format. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A --------- Co-authored-by: zhanghui <23442919+gloryfromca@users.noreply.github.com> Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
…EverMind-AI#362) ## Summary Add an end-to-end trajectory workflow for capturing, packaging, reporting, replaying, and permanently guarding Raven agent runs. - Give every tracing span an attempt id, with explicit multi-turn attempt scopes and a single-turn fallback. Add append-only pass/fail/infra verdicts, trajectory pins, rotation-transparent lookup, and canonical attempt resolution. - Add `raven trajectory` commands to list, label, pin, save, report, replay, and minimize attempts. Saved bundles are self-contained and include rewritten artifact references, session history when available, verdicts, and a manifest. - Add three-layer report redaction: exact replacement of known config and environment secrets, pattern-based credential removal, and a residual scan for human review. Reports are produced as local tarballs through a pluggable uploader interface. - Add deterministic replay through the live agent harness with recorded model replies and stubbed tool results. Replay never executes real tools, uses a temporary workspace, suppresses its own tracing, and reports strict or warning-mode divergences under narrow normalization rules. - Add minimized and redacted trajectory cassettes, a YAML assertion DSL, and pytest discovery for permanent harness regression cases. Assertions can require a specific first divergence and verify the fixed live message or tool request at that point. - Harden all externally derived paths and replacement directories, including canonical ids, bundle output paths, cassette destinations, artifact traversal, and escaping symlinks. - Change streamed response aggregation to preserve a missing upstream finish reason as `unknown` instead of fabricating `stop` or `tool_calls`, keeping recorded trajectories faithful to the provider response. ## Type - [ ] Fix - [x] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - `uv run --frozen pytest tests/ -q --ignore=tests/integration`: 6862 passed, 33 skipped, 13 deselected, and 1 unrelated existing theme-rendering test failed. The branch does not modify that test, the theme implementation, or dependency manifests. - `uv run --frozen pytest tests/test_cli_theme.py::test_bold_accent_renders_styled_not_bare -q`: the same unrelated theme-rendering failure reproduced in isolation. - `uv run --frozen ruff check .`: passed. - `uv run --frozen ruff format --check .`: 937 files already formatted. - `make check-large-files`: passed. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed ## Risk - The new CLI and trajectory formats are additive. Existing spans gain an `attempt.id` attribute, and readers retain compatibility with older spans by falling back to the trace id. - Report and cassette payloads can contain complete model and tool text. Redaction runs on copies, excludes unverifiable binary files, records its actions, and requires residual review before sharing or committing data. - Replay is isolated from real tool execution, the real workspace, session persistence, and tracing. Its normalization deliberately covers only values regenerated on every run so real behavior changes remain visible. - The only intentional existing-path behavior change is the `unknown` fallback for a missing streamed finish reason. Production consumers were checked for dependence on the previous synthesized values. - Rollback is to revert this PR. Existing trajectory state files and directories are additive and can remain unused after rollback. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A --------- Co-authored-by: 江国庆 <guoqingjiang@deepglint.com> Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com> Co-authored-by: zhanghui <huizhang1995@gmail.com>
…urn (EverMind-AI#361) ## Summary Two presentation defects in the TUI chat transcript. They ship together because both change what the virtualized transcript has to reserve rows for, and reviewing the row arithmetic once is safer than twice. **The cover was never a screen.** It is transcript row 0 -- a message with `kind: 'intro'` that `appLayout` renders as the wordmark plus the session panel -- and `capHistory` pinned it at index 0, so it was exempt even from the history cap. Measured against the real renderer it occupies 35 terminal rows, while `estimatedMsgHeight` guessed 9. So the first answer was appended *below* a full screen of cover art instead of replacing it, and scrolling back up always found it again. `hideIntroAfterFirstTurn` drops it from the view once a `user` or `assistant` row exists. The row itself stays in `historyItems`, because the late `session.info` event patches itself onto that row, and `/export` and the slash handlers read the session's own list rather than whatever is on screen. The trigger is deliberately a turn and not "any other row": startup notices go through `sys()` as `role: 'system'` and slash output is `kind: 'panel'`, so gating on those would mean a host that warns about its credentials on boot never shows a cover at all. Both the rows and the layout's copy come from the one filtered array. `appLayout` compares `row.index` against indices it derives from `transcript.historyItems`, so filtering one and not the other would move the inter-turn separator and the todo panel one row off. **The person's own message now renders on a filled background,** so a turn reads as an inserted card rather than one more line of prose. `userBg` is a curated literal per tier and scheme, the way the rest of the reduced-tier palette is carried, and is skinnable through `ui_user_bg`. Below 256 colors there is no shade between black and brightBlack, so the fill is skipped and the prompt chevron carries the row alone. The two padding rows are drawn at every tier so `estimatedMsgHeight` can reserve a row count without reading the terminal's color capability; it now reserves four rows for a user message rather than two. One testing note worth carrying forward: `ink-testing-library` brings its own reconciler and silently drops both foreground colors and background fills, so a fill is only observable through `renderSync` writing real escape codes to a stream. An early version of these tests passed against nothing. ## Type - [ ] Fix - [x] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other Mixed in practice: the cover half is a fix, the filled block is new behaviour. ## Verification Run in a clean worktree at this branch, with `packages/hermes-ink` built from this base rather than borrowed, so the render assertions exercise the Ink this branch ships: - `npm run type-check` -- clean. - `npx eslint src/` -- 0 errors, 22 warnings, all pre-existing. `useMainApp.ts` carries two of them before and after the change (same rules, shifted 12 lines by the inserted comment), confirmed by linting the pristine file from `main` and diffing. - `npx vitest run --no-file-parallelism` -- 87 files, 996 passed, 13 skipped, 0 failed. - `scripts/check_commit_messages.py main..HEAD` -- exit 0. - Each of the three mechanisms reverted **in isolation** to prove the tests are not theatre: neutering `hideIntroAfterFirstTurn` reddens the two cover tests; removing only the `backgroundColor` prop while keeping the padding reddens the fill test; changing the reserved rows from four back to two reddens both height tests. Suites that exercise the changed code directly: `messages.test.ts` (the filter, and the block rendered through `renderSync`), `virtualHeights.test.ts` (the reserved rows), `theme.test.ts` (the token per tier, and the fill gate). - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed `ui-tui/CONTEXT.md` gains **Prompt Block** and **Cover**, since both are named in the code and neither existed as a documented term. ## Risk Visible change to every chat transcript: a user message grows from three rows to five (two of them the filled padding), and the cover stops being visible after the first turn. Nothing is removed from the session -- the cover row is still in `historyItems`, so `/export` and session save are byte-identical. Rollback is the revert of this one commit; there is no migration, no persisted state, and no wire-format change. A skin that dislikes the shade can set `ui_user_bg` without a code change. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes Known gaps, disclosed rather than left for review to find: - The index alignment between `virtualRows` and the layout's `historyItems` is held by construction and by a comment, not by a test; there is no existing harness that mounts `useMainApp` against a gateway. - `theme.ts` cites `scripts/gen-color-palettes.mjs` as the provenance of the reduced-tier palettes and a `docs/tui-color-problem/tokens.md` for the tier-2 values. Neither holds today: that script's embedded source palette is a stale green one, its algorithm maps `#161b22` to `ansi256(16)` where the file has `ansi256(234)`, and the doc does not exist. The values are hand-curated, and the ones added here follow the existing neighbours. Both stale comments are left alone rather than widen this diff. ## Related Issues N/A Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com> Co-authored-by: zhanghui <huizhang1995@gmail.com>
EverMind-AI#354) ## Summary `ask_user` advertised things it did not do, and a batch cost the user more than one wait. This closes both, and gives the prompt the fields it needs to render a batch honestly. - **Two schema fields nothing read.** `multiple` and `custom` were declared per question and never passed to the broker, so a model asking for a multi-select got a single-select and never learned why. Both are removed. Multi-select is deliberately not implemented here: it would need a new answer syntax on the chat-channel surface, which has no dialog to hold a multi-select, and that is a larger decision than removing a field that never worked. - **One deadline per call, not per question.** A batch was N round-trips with N independent timeouts, so a three-question call could hold a lane for three times the surface's wait. The call now shares one budget, and a spent budget stops the batch instead of opening a fresh wait on every question left. - **The budget is configuration.** `tools.ask_user.timeout` (600s, must be positive) reaches the tool through `AgentLoop`, which is where the config already is. It deliberately does not read config at the transport: the TUI builds its broker before any config is loaded, and reading it there put a schema-validation failure in the path of the RPC server coming up. - **Calls that would waste the user's time are rejected before rendering**, with a message that steers the retry: more than four questions, a duplicate question text, or a question with exactly one option, which is not a decision. A duplicate option label is a typo with one obvious reading, so it is deduped rather than rejected. Zero options remains a free-form question. - **`header` and `recommended`.** A short chip label, and a 0-based index into `options` naming the option the agent would pick. When nobody answers, the text the model reads now names that option; before, the only signal was that no answer arrived. - **A recommendation is resolved against the options as submitted.** The index counts what the caller sent, but the list was deduplicated before the index was resolved against it. With `["a","a","b","c"]` and `recommended: 2` the caller means `"b"` while the broker was handed `"c"`, so the surface marked the wrong row and a timeout told the model the wrong intended fallback; an index past the deduped length (`3` on that list) failed the range check and the recommendation was dropped without a word. The label is now resolved before dedup narrows the list. Dedup keeps every distinct label, so the resolved label is always still one of the choices the surface can mark. - **An undeliverable question is reported, not dropped.** A question for a conversation with no live source was dropped with a log line the broker could not see, so the round-trip waited out its whole budget on a question nobody would ever see. The channel adapter now raises `QuestionUndeliverableError` and the broker fails safe at once. - **The prompt renders the batch.** Position in the call, the questions still to come, the recommended option marked, and the remaining budget counting down. Tab attaches a note to a selection, so an answer no longer has to be either a choice or free text. On a chat channel the position rides in the message text. - **The countdown renders whole seconds.** `timeout_s` is a remaining-time subtraction, so a 90-second budget arrives as `89.99999912502244` and the prompt decrements that same fractional value once a second. The formatter passed it straight through, so the line read `1m 29.99999912502244s` on every tick and widened the prompt that the padding exists to keep steady. The value is now ceiled before the branch is chosen, since ceiling inside the branches renders a ceiled `59.7` as `60s` rather than `1m 00s`. - **The `Other` row answers to its own number.** This one predates the rest of the branch. The options list draws `Other` as one more numbered row, but the quick-pick handler bounded itself at the number of real choices, so the last row was numbered and unreachable -- and worse than inert: the keystrokes that followed were swallowed by the options handler, so the next Enter submitted whichever option was still highlighted. Someone who thought they were typing a free-form answer sent a selection they never made. Its number now opens the text input, which is what Enter on that row already did, and the hint names the range it can take. The wire payload gains `header`, `recommended`, `timeout_s`, `index`, `total` and `batch`. `clarify.request` is a notification and is not part of `rpc-schema/openrpc.json`, so no generated artifact changes; `npm run lint:rpc` confirms `generated.ts` is still in sync. `deep_research` shares the same broker and passes none of the new fields, so its prompt is unchanged except that it now shows the countdown, which was always the real deadline. `timeout_s` stays fractional on the wire deliberately: it is also what the model is told about the budget, and rounding it at the source would change that value to serve a display concern. The formatter is the layer with a width to protect. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other Mostly fixes; the prompt affordances and the config field are additive. ## Verification Every new test was watched failing before the code that makes it pass. Rebased onto `22b71765`, which collapsed the earlier `Merge branch 'main'` commit into a linear six-commit history. The base gained EverMind-AI#361 and EverMind-AI#362; the only file this branch and those commits both touch is `raven/agent/loop/main.py`, and the replay was clean. `git merge-tree --write-tree` needs git 2.38 and this box has 2.34.1, so the pre-push check was that file-overlap table plus a full re-run at the new head rather than a dry-run merge. Everything below is from the new head, not carried forward. ``` python -m pytest tests/ -q 82 failed, 6576 passed, 47 skipped, 13 deselected # same suite on the new base 22b7176, in a separate worktree: 82 failed, 6545 passed, 47 skipped, 13 deselected # full failing-ID lists from both runs, sorted and diffed: diff branch_fail.txt base_fail.txt -> identical (0 introduced, 0 fixed) ``` The 82 failures are inherited: the base fails the same 82 IDs on this box, and the sets are identical rather than merely the same size. They are `test_tui_rpc_session.py` (58), `test_cli_cron_commands.py` (7), `test_cli_import_commands.py` (5), `test_tui_commands_error_codes.py` (3), `test_cli_onboard_commands.py` (3), and 6 more spread across 5 files. The +31 passing tests are the ones added here. Suites that exercise the changed code directly: ``` python -m pytest tests/test_ask_user_tool.py tests/test_question_broker.py \ tests/test_cli_gateway_commands.py tests/test_config_schema.py -q 81 passed npm run test -- --run src/__tests__/clarifyPrompt.test.tsx 11 passed npm run type-check -> clean npm run lint:rpc -> OK: generated.ts in sync python -m ruff check raven tests -> All checks passed! python -m ruff format --check raven tests -> 849 files already formatted python -m scripts.check_commit_messages github/main..HEAD -> exit 0 scripts/check_large_files.py -> exit 0 ``` Beyond the suites, the tool was exercised over the real path -- a real `ToolRegistry` (so `cast_params` and the schema validator run), a real `QuestionBroker`, and answers delivered through the real `clarify.respond` handler on a real `Dispatcher`: - the three rejections emit **zero** prompts, so a malformed call costs the user nothing, and the steer text reaches the model intact through the registry; - a batch answered by a responder taking 0.25s per question is handed `10.0000s`, `9.7495s`, `9.4991s` -- each question inherits what the last one left; - a batch whose budget is spent emits **one** prompt, not one per question; - an undeliverable question returns in `0.001s` against a 30s budget. `test_registry_dispatch_and_the_real_clarify_respond_route` was added from that exercise, because no committed test drove the registry before. It was proved non-vacuous by renaming the `recommended` keyword the tool passes the broker and watching it go red. The two review fixes were each proved the same way: - The recommendation cases were run against the deduped resolution and failed with `assert 'c' == 'b'` (wrong label) and `assert '' == 'c'` (recommendation dropped), then passed once the resolution moved ahead of dedup. `test_out_of_range_recommended_index_is_ignored` passes unchanged -- it has no duplicates, so it never encoded this. - The countdown cases were run against the pass-through formatter and failed on `expected ... to match /1m 30s/` and `/1m 00s/`, covering both the fractional payload and the `59.7` boundary that ceiling creates. `prompts.tsx` and `clarifyPrompt.test.tsx` are reported by Prettier, and were before this branch touched them: the hunks it wants are at lines this branch does not change (a JSX ternary and a `renderSync` call), so they are left alone rather than reformatted into this diff. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed `docs/Proactivity-Implementation.md` documented the two removed fields and the old narrow payload, so that paragraph is rewritten. `ui-tui/dist/entry.js` is gitignored, so testing the prompt by hand needs `npm run build --prefix ui-tui` first. ## Risk User-visible changes: - A model sending five questions, a duplicate question, or a one-option question now gets an error instead of prompting the user. The error text tells it what to do instead. - A batch that used to get a fresh timeout per question now shares one. A slow user answering question 3 of 4 past the budget gets the rest recorded as unanswered rather than being asked. - The prompt shows a countdown where it showed none, including for `deep_research`, and that countdown now reads in whole seconds. - A recommendation sent as an index past the deduped option count used to be dropped silently and now resolves. That is what the schema promises, but it is a real change for a caller that had been sending such an index. - Typing the `Other` row's number now opens the text input instead of doing nothing. Rollback is the six commits; nothing is persisted and no format changes on disk. The config field defaults to the previous 600s, so an untouched config behaves as before. Checked and deliberately not fixed, to keep this diff to one subject: - `QuestionBroker.await_question` still returns its default on `asyncio.CancelledError`, which swallows an outer cancellation. It sits beside the handler added here, but changing it means auditing how the agent loop reacts to a cancelled turn. - The gateway's inbound gate calls `pending_req(cid)` and then `reply(...)`. If the question times out between the two, the message answers nothing and starts no turn. `reply` already returns a bool, so using it as the condition would close this. - The registry's schema validator does not implement `maxItems`, so the cap in the schema is advisory to the model and `_prepare` is what enforces it. That is deliberate -- the model gets the steer text rather than a generic validation error -- and a test pins the two statements of the cap together so they cannot drift. - `ApprovalPrompt` in the same file hardcodes `1-2 quick pick`, which is correct for its two fixed options and has no `Other` row, so it is not the same bug. - `approvalRemainingSeconds()` feeds the approval countdown in the same file and is already `Math.max(0, Math.ceil(...))`, so it needs nothing here. It is also where the `ceil` convention comes from: the clarify countdown now rounds the same direction as the approval one rather than inventing a second rule. The two are still not made to share a formatter -- one takes an absolute deadline and the other a remaining duration. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes No new secret, no new surface, and no asset moved onto an unauthenticated one. The new config field is a timeout. ## Related Issues N/A --------- Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
…lipboard path lying (EverMind-AI#360) ## Summary A TUI that enables mouse tracking owns the drag, so the terminal never builds a native selection and its own copy shortcut has nothing to copy. Copy-on-select is what makes a transcript selection copyable at all -- but the subscription bailed out on `!isMac`, so on Linux and Windows a drag highlighted text and copied nothing. This lifts that gate, and fixes what was found while making the result observable. **Copy-on-select on every platform.** The subscription moves out of `useMainApp` into `subscribeCopyOnSelect()`. Inlined in a hook that needs a live gateway to start, none of its four guards could be tested; each now has a case, and each was checked by removing only that guard and watching the suite go red. The read of the bus state stops being an unchecked cast: the ambient `useSelection()` declaration types it as `unknown`, so the module narrows it instead. **The copy is reported to the transcript.** Nothing on screen changes when a drag ends, so a line is written once a clipboard path has actually taken the text. The callback fires on a non-empty result only, since `copySelectionNoClear()` resolves to an empty text when nothing reached the clipboard. The first report of a session carries the path caveat, later ones stay terse: OSC 52 is the one path a terminal can still refuse, and the first copy is when a user is looking for the reason a paste came up empty. **The reported path comes from the write, not from the environment.** A path predicted from env state cannot be right in general, because the environment does not record what happened: inside tmux a `load-buffer` that fails falls through to raw OSC 52 and leaves `TMUX` set exactly as the case that worked. With a stale tmux socket the bytes went out as OSC 52 while both callers said "copied to the tmux buffer", pointing the user at a `set-clipboard` setting that was never involved. `setClipboard()` already computed the three facts that decide this -- whether native was attempted, whether the buffer loaded, whether a sequence was emitted -- so it now reports which one took the text, and null when none did. The value travels with the copied text out of `copySelectionNoClear()` and `copySelection()` to the two call sites that display it. The predictor is deleted rather than left exported from the package: after the rewiring it had no callers, and a helper that can name a path the data did not take is a defect waiting for the next caller. Its env-matrix cases were assertions about the predictor itself, so what replaces them drives the real `setClipboard()` with `tmux` stubbed, including the failed-load fallback. Reporting an observed path also has to wait for the observation. On Linux the native tool is discovered by a probe, and `copyNative()` answered the first call before that probe settled -- a display server means a tool could exist, not that one does. Naming the path from that optimism claimed a native write on a machine with `DISPLAY` and no `wl-copy`, `xclip` or `xsel`, and the same call reported failure once the probe finished. The first call now answers with the probe's own result. It is still started before the tmux await, so the probe runs alongside `load-buffer` rather than ahead of the report, and later calls still answer synchronously from the cache. The copy never ran ahead of that probe either -- the tool is spawned inside it -- so only the report did. **The path caveat is scoped to a session, not to the process.** The first copy of a session carries the caveat and later ones stay terse, but the flag holding that lived in a hook that outlives the session: `newSession()` and `resumeById()` replace `ui.sid` without remounting `useMainApp`, so every session after the first opened with the terse line and the user never learned an OSC 52 paste can come up empty. The tally moves into a reporter keyed on a session identifier, so the state and the thing it is scoped to live together and the boundary is unit-testable. The hook reads the sid through `getUiState()`, which keeps a session change from tearing down the bus subscription. **The report must not overclaim.** OSC 52 hands bytes to the terminal and the terminal decides whether to keep them, and nothing in the write path caps or chunks the payload. Measured on the real code: one 200x50 viewport of CJK is a 40 KB escape sequence, and a selection dragged through 2000 rows is 536 KB in a single sequence. Terminals drop an oversized sequence without a word, and `setClipboard()` reports success for all of it because bytes were written to stdout. So that path reports what it sent; native and tmux, which really did write a clipboard, still say copied. The count was UTF-16 code units, which reads three emoji as six characters and a combining accent as two, so it is counted by grapheme now. CJK was already right, being one code unit per character. **The documented env knobs were dead.** The OSC 52 override and the clipboard debug switch were readable only under the upstream `HERMES_TUI_` names, while every knob this project documents uses `RAVEN_TUI_` and `/copy`'s own failure hint names `RAVEN_TUI_FORCE_OSC52` and `RAVEN_TUI_DEBUG_CLIPBOARD`. Following that hint changed nothing. Both spellings are now read, `RAVEN_TUI_` first, with the `HERMES_TUI_` names kept as aliases so an environment that worked before still works. The debug switch moves behind `clipboardDebugEnabled()` so its four call sites cannot drift apart. ## Type - [x] Fix - [x] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other Two boxes: the platform gate is the feature, the clipboard-path and env-name defects are fixes found while making it observable. ## Verification Run from `ui-tui/`, on Linux, node 22, at branch head after the rebase. ``` npx vitest run --no-file-parallelism Test Files 91 passed (91) Tests 1066 passed | 13 skipped (1079) npx tsc --noEmit -p tsconfig.json clean, exit 0 npm run lint 22 problems (0 errors, 22 warnings) python3 -m scripts.check_commit_messages github/main..HEAD exit 0 ``` The suite is run with `--no-file-parallelism` deliberately: the ink render tests fail under default worker parallelism at this suite size, and no CI job runs them. The 22 lint warnings are the baseline, not new: 22 before the branch and 22 after, none of them in a file this branch touches. Every file this branch touches passes `prettier --check`. Two of them did not at first, and that was this branch's doing rather than the base's: dropping a name from the `@hermes/ink` import and adding a wide function signature left both inside the print width while still wrapped. They are formatted in their own commit. The files already unformatted on `github/main`, `chatStream.ts` among them, are left alone rather than reformatted into this diff. Suites that exercise the changed code directly: `src/__tests__/copyOnSelect.test.ts` (12 cases), `src/__tests__/clipboard.test.ts` (30 cases), `packages/hermes-ink/src/ink/termio/osc.test.ts` (39 cases). An environment gap that made the first pass of this verification worthless, since it would silently affect anyone reviewing from a worktree: `ui-tui/node_modules` here was a symlink to another checkout's, so `node_modules/@hermes/ink -> ../../packages/hermes-ink` resolved against the symlink target and landed on that other checkout's copy of the package. `tsc` and the dist build therefore read an unmodified `hermes-ink` while the app code under test was the edited one. Fixed by giving the worktree a real `node_modules` whose `@hermes/ink` points at its own package, confirmed with `tsc --listFiles` naming the edited `ink.tsx`, then rebuilt and re-ran everything. Every number above is from after that. New tests were proved load-bearing rather than assumed: - Each of the four guards in `subscribeCopyOnSelect()` was removed on its own and the suite watched go red (1, 1, 2 and 2 failures), then restored. - The three cases that assert a copy did *not* happen are each paired with a positive assertion on the same subscription, because a bus that was never wired up satisfies the negative half by itself. - The path-reporting cases were checked by reverting the derivation to the deleted predictor's rule and watching the failed-load case report `tmux-buffer` again. - The Linux first-probe case was checked by reverting `copyNative()` to its optimistic `return true` and watching it fail on `success: true, path: 'native'` where it now asserts `success: false, path: null`. Establishing that this one was introduced here rather than inherited took running the same scenario against `github/main`, which reported `path: 'osc52'` for it: `success: true` with nothing written predates the branch, the native claim does not. - Of the three session-scoping cases, one is load-bearing: reverting the reporter to a process-wide flag fails "spends the caveat once per session". The other two -- a session returned to, and two reporters not sharing a tally -- pass with the flag as well, and are there to pin the property against a later refactor. Said plainly rather than implying all three prove the fix. One pre-existing assertion is flipped rather than kept. It pinned the terse copy-on-select line to "copied" on the OSC 52 path, which is the overclaim this branch removes, so the assertion encoded the defect instead of the behaviour worth holding. Rebased onto `22b71765`. The base gained EverMind-AI#361, EverMind-AI#362 and EverMind-AI#355; the only file this branch and those commits both touch is `ui-tui/src/app/useMainApp.ts`, where EverMind-AI#361 reworked the cover and prompt block while this branch replaced the copy-on-select effect in the same hook. The replay was clean and the whole verification above was re-run at the new head rather than carried forward. `git merge-tree --write-tree` needs git 2.38 and this box has 2.34.1, so the check was the file-overlap table plus the re-run, not a dry-run merge. Manual check of the rendered artifact, since `raven tui` runs the prebuilt `ui-tui/dist/entry.js` and nothing rebuilds it automatically: `npm run build`, then confirmed the new strings are in `dist/entry.js`. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed No doc change. `CONTEXT.md` has no clipboard or selection entry, and copy-on-select was already the term in use in the vendored fork, so nothing new is coined. The `/help` hotkey table is deliberately untouched: select-then-copy is ordinary terminal behaviour and does not need advertising. ## Risk Behaviour changes for users who are not on macOS: a drag now writes the clipboard and adds a transcript line. On macOS nothing changes except the wording of that line. `/copy` keeps its shape and gains the path the write actually took. One API change inside the vendored fork: `Ink.copySelection()` and `copySelectionNoClear()` return `{ text, path }` instead of the copied string, and `getClipboardPath()` is gone from the package's exports. Internally `copyNative()` may now answer with a promise on Linux's first copy, which `setClipboard()` awaits after starting the tmux write; no caller outside that function sees it. Both are consumed only within this repo, and the ambient declaration, the app interface and the two call sites move with them. Rollback is per concern. Reverting the feature commit restores the platform gate and leaves the fixes, which stand on their own. Reverting all of them returns the files to their base state; no data, config or on-disk format is involved, and no migration exists to undo. Checked and deliberately not fixed: - The OSC 52 write has no size cap and no chunking, so a large selection is handed to the terminal whole and silently dropped past whatever that terminal tolerates. This is why the OSC 52 path reports what it sent rather than claiming a copy. Capping or chunking it means changing the vendored write path and picking a threshold per terminal, neither of which belongs in this branch. - `Ink.copySelectionNoClear()` calls `getSelectedText()` outside its own `try`, so a throw there rejects the promise. The base already left that rejection unhandled at a bare `void` call, and adding a `.then()` does not change it. Fixing it means restructuring a vendored method this branch has no other reason to touch. - The effect's dependency array gains `sys`. It is provably stable (`useCallback` over `appendMessage`, itself `useCallback` with `[]`), so the subscription is not re-created in practice. If it ever were, the version de-dupe would reset and one extra line could be written; the path caveat would not repeat, because that tally now lives in a ref that survives the effect. - Running `/copy` before the first drag of a session shows the path caveat twice, once from each lane. Sharing that state between an explicit command and an automatic one to save a duplicated sentence is not worth the coupling. - Two narrative comments in `copyOnSelect.test.ts` were reduced rather than removed: each kept the constraint it was wrapped around (that the platform case can only mean something on a non-macOS runner, and that an empty result is the whole signal that nothing reached the clipboard) and lost the "defect this closes" framing. Reading section 1.1 as rejecting the task-context wrapper rather than the constraint underneath is a judgement call, flagged as one. The selection bus is faked in the new tests. The real one returns no-ops outside a fullscreen Ink instance bound to a TTY, so there is no way to drive it from vitest; the fake mirrors the ambient `useSelection()` contract, including `getState(): unknown`, which is why the narrowing is exercised rather than assumed. Flagging it because a hand-rolled stand-in for a real type is exactly the blind spot that hides a defect. Security: no new surface. Selected transcript text already reached the terminal through this path on macOS and through `/copy` everywhere; the same bytes now travel on the other platforms, on a user-initiated drag, which is what a native terminal selection would have done anyway. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A --------- Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
## Summary Bump the package version from 0.1.12 to 0.1.13 for the next release. Only pyproject.toml and uv.lock change. Core changes since v0.1.12: - Conversations now keep their selected model, with the follow-up lifecycle and recovery paths aligned to that ownership. - The TUI handles fast typing, prompt layout, and cross-platform copy-on-select behavior more reliably. - ask_user now keeps its timeout, validation, and delivery contracts aligned across runtime paths. - End-to-end trajectory debugging and regression workflows are available. - Configuration and Skill Hub failures now surface clearer, bounded errors, and EverOS is updated from 1.2.1 to 1.2.3. ## Type - [ ] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [x] Other ## Verification - `uv lock` - resolved 193 packages and updated raven v0.1.12 to v0.1.13 - `uv lock --check` - resolved 193 packages successfully - `uv export --locked --all-extras --no-hashes --no-emit-project -o /tmp/raven-0.1.13-constraints.txt` - resolved 193 packages and wrote 624 lines - `uv run pytest tests/test_package_skeleton.py::test_package_imports tests/test_cli_smoke.py::test_version_flag_matches_installed_metadata -x` - 2 passed - `uv run raven --version` - reported Raven v0.1.13 - `git diff github/main...HEAD --check` - passed with no output - [x] Relevant tests pass locally - [ ] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed No lint, type, documentation, or screenshot changes are needed for this version-only bump. ## Risk - [ ] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes This changes package metadata only. Rollback by reverting the version bump. ## Related Issues N/A Co-authored-by: zhanghui <23442919+gloryfromca@users.noreply.github.com>
…ities visible (EverMind-AI#312) ## Summary A WeChat user asked for a recommendation and the bot answered "the web search tool is not configured here". That sentence was not the model's own phrasing -- it was the tool's error text, relayed outward. `web_search` needs a Serper key and that deployment has never had one, but the tool was registered unconditionally, so on every search-shaped question the model saw it, reached for it, and passed the setup error on to whoever was in the chat. Withholding the tool fixes that and creates a second problem: an unregistered tool is invisible. Nothing in a running Raven then says the capability exists at all -- the model is never offered it, no document lists it, and `raven doctor` reports on providers and memory but has never mentioned tools. The only way to learn that web search is one account and one edit away was to read the source. This does both halves, because either alone leaves a real gap: the deployer is told what this install can and cannot do, and the model is still not offered a tool it cannot run. ### Withholding the unusable tool `web_search` is gated on a resolved key, in the main loop and in the sub-agent surface (`subagent/manager.py`) -- a sub-agent that reaches for a search it cannot run reports the failure to its caller, and that text lands in the parent turn, the same leak one level down. The gate asks the tool, not the config. `WebSearchTool.api_key` resolves at call time from the constructor value *or* `SERPER_API_KEY`, so reading `tools.web.search.apiKey` alone would withdraw a working tool from any deploy that exports the variable and configures nothing. The tool's error message also hard-coded `~/.raven/config.json` while the gateway runs with `--config` elsewhere, so following it meant editing a file the process never reads. It names the path actually in force now. That text is reachable only if the key disappears after registration, which is exactly why it should be right: it is the message for the case the gate cannot cover. ### Three rules, and nowhere to read them Registration is decided per family, each a different shape: | family | rule | |---|---| | `web_search` | a resolved key, asked of the built tool | | `web_fetch` | nothing -- always registered, a key only improves extraction | | media x3 | an `api_key` *or* a `model`, either counting as configured | For media those are two questions, not one. A section naming only a model is registered, because a model alone counts as asking for the tool, and then every call returns a missing-key error. Whether a capability is *offered* and whether it *works* come apart there, and a report treating them as one fact ticks a capability that cannot run. Each rule is defensible where it sits. What is missing is anywhere to read them. Providers had the same sprawl once and answered it with `providers.auth`: a declarative table plus `credential_status` as the single authority, with an AST invariant enforcing that authority, because six surfaces had answered the same question six ways and each looked reasonable alone. Tools never got the equivalent. ### What this adds **Each tool answers for itself,** twice where the questions differ. `WebSearchTool.is_configured` reads the config value *or* `SERPER_API_KEY`, because those are two sources and only the tool consults both. The media base answers `is_configured` on a model *or* a key -- which is what stops an OpenRouter credential set for chat from silently switching on three tools that bill per call -- and answers `has_key` separately on the chain it actually resolves at call time: its own section, the borrowed provider key, then `OPENROUTER_API_KEY`. Both rules live with the credential they read. **`capabilities.py` describes the five** for a human deciding what to set up: what each does in one line, how much work it is (nothing / reuse a credential you already have / obtain an account), where the key goes, where to get one, and what it costs. It rules on nothing -- `is_configured` and `has_credential` both ask the tools -- so it cannot become a second opinion. **`raven doctor` grows a section**, listing every capability configured or not, ordered by how much the deployer has to do: ``` (markers render as a green check and a yellow bang; spelled [ok] and [!] here to keep this description ASCII, since it becomes the squash commit body) Tool capabilities web_fetch: [ok] Read a web page the agent already has the URL for image_generate: [ok] Generate an image (borrowed: providers.openrouter.apiKey) text_to_speech: [!] Generate speech from text no key resolves; calls will fail set: tools.media.speech.apiKey or env: OPENROUTER_API_KEY video_generate: - Generate a video switch on: tools.media.video.model key: reusing providers.openrouter.apiKey Billed per call; needs prepaid OpenRouter credit. web_search: - Search the web set: tools.web.search.apiKey or env: SERPER_API_KEY key from: https://serper.dev 2 capability(s) available but not set up; the agent is not offered them. ``` Three deliberate details. Naming the credential is load-bearing in both directions. A row that cannot distinguish a reused credential from a missing one sends someone to create an account they already have -- and a row that claims a reuse with nothing to reuse is worse, because acting on it means setting a model, getting a registered tool, and watching every call fail on a credential they were told they had. So the reuse line prints only when a key is genuinely there to pick up, and a capability already in that broken state says so outright instead of showing a satisfied tick. The credential is named at the path that holds it. For the media family `config_path` names the *model*, so reusing it as the key source pointed the deployer at a line with no credential in it. And each fact is on its own line rather than in a sentence, because the terminal wraps a long line mid-path and a config key broken across two rows cannot be copied, which is the only thing that row is for. Nothing here moves the exit code, including the warned row. An install without image generation is a choice, not a fault, and a doctor that fails on it teaches people to ignore doctor. The half-finished one is arguable -- the memory section does exit non-zero for a role the user configured that the server could not build -- but that failure is silent where it happens, recall just returning nothing, whereas this one returns an error string to the model on every call. Say the word and it becomes an exit code instead. ### Not in this change Registration still lives in `AgentLoop`. Having it read the table is the point of this shape and removes the last duplicate reader, but it edits `agent/loop/main.py`, which is under active change, and it is worth doing on its own once that settles. Until then the table is a description, and the tests below are what keep it honest. `deep_research` is deliberately absent: it is moving to the sub-agent surface and its tool is going away. ## Type - [ ] Fix - [x] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other Mixed on purpose: the first commit is a fix and the rest are the feature it made necessary. Splitting them would land a change that hides a capability without landing the one that makes it discoverable. ## Verification ``` make lint exit 0 make build exit 0 npm run test --prefix ui-tui 969 passed, 13 skipped uv run --all-extras pytest -q 6186 passed, 35 skipped, 1 failed uv run pytest tests/test_tool_capabilities.py \ tests/test_cli_doctor_commands.py \ tests/test_agent_loop_web_tools.py 64 passed uv run --extra dev ruff check raven tests scripts All checks passed uv run --extra dev ruff format --check 828 files already formatted npx commitlint --from origin/main --to HEAD exit 0 scripts/check_commit_messages.py origin/main..HEAD exit 0 ``` The one failure is `test_read_file_image.py::test_an_attachment_that_cannot_be_ read_costs_a_note_not_the_turn`, and it is not from this branch: it fails the same way on `main` at `1cb604a` with these commits absent. The case makes a file unreadable with `chmod 000`, which does not block a root user, so it fails for anyone running the suite as root and passes in CI. This branch does not touch that file or the code under it. The capability tests drive a real `AgentLoop` and compare what it registered against what the table predicts, rather than asserting the table against itself. The gated set is derived -- tools present once credentials are supplied, absent without -- so a sixth gated tool whose author forgets the table fails here rather than going unnoticed. That last claim was false when first written, and the last commit is what makes it true. The fixture listing the media tools by hand never switched a fourth one on, so a new one never joined the gated set and the assertion held over an already-incomplete table. EverMind-AI#305, which adds a MiniMax voice-clone tool, is that case: merged against this branch the assertion passed. Read from `MediaGenConfig` instead, it fails and names the tool -- `gated but undeclared: ['voice_clone']`. Which means whichever of the two lands second turns this red, deliberately. The fix is one table entry, and it needs the rule that PR settles: `voice_clone` counts as configured on `api_key or api_base or model` in `effective_media_config` while registration still gates on `api_key or model`, so an `apiBase`-only install with no MiniMax key is configured by one rule and withheld by the other. That is the divergence this table exists to make visible, and it is worth resolving there rather than papering over here. Fifteen mutations, each caught: ``` the web_search gate removed from the main loop 3 failed the same gate removed from the sub-agent surface 1 failed media rule reads only the key 7 failed web_search rule reads only the config 1 failed a tool removed from the table 6 failed the doctor section not rendered 6 failed doctor lists only configured tools 4 failed unconfigured treated as a failure 17 failed the reuse line is printed unconditionally 1 failed the key source falls back to the model path 3 failed a keyless registered capability is not flagged 1 failed has_key always answers yes 1 failed has_credential collapses into is_configured 1 failed the credential chain drops OPENROUTER_API_KEY 3 failed the reuse check ignores the environment 3 failed ``` Four of those tests exist only because a mutation pass found the earlier versions insufficient, and the last rounds are why the shape changed. A media case that sets both a model and an OpenRouter key proves nothing about the "or model" half: the borrow fills the key in, so a rule reading only the key still answers correctly. The case that pins it has nothing to borrow. The sub-agent case is the same argument applied to the second call site: with the gate present only in the main loop, every test above still passes, because nothing was watching what the sub-agent surface registers. More usefully, mutating `has_key` to always answer yes changed no test at all in the first version, which said the ruling was not load-bearing: the doctor was inferring "no credential" from an *empty source string* rather than from the tool's answer, so a fourth credential source would have been reported as a missing one. `has_credential` is now its own fact, asked of the tools, and that mutation fails. Separately, autouse fixtures clear `SERPER_API_KEY` and `OPENROUTER_API_KEY`, without which several of these pass for the wrong reason on any machine where a developer exported one. Rendering was checked against a real config in six states -- nothing set, a provider key present, a model with nothing to borrow, a tool with its own key, a model plus a borrowable key, and an exported variable as the only source -- rather than only asserted on. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk One behaviour change: `web_search` is no longer offered to the model when no key resolves. That is the fix. A deployment that has a key, in config or in the environment, is unaffected; one that has none was getting an error string in place of an answer. The rest is additive. No other registration logic changed, so which tools an agent is offered is otherwise exactly what it was; `is_configured` moved the existing predicates onto the tools without altering them, and the mutation results above are what pins that. `has_key` and `_resolve_key` are new names for the chain `api_key` already resolved -- the property calls the extracted one, so callers see the same answers. `raven doctor` gains a section and no new exit code. Its zero-network guarantee holds: the table reads config and environment only. Two allowlist entries were added to `test_only_the_auth_module_decides_configuredness_from_a_key`, argued in place: `subagent/manager.py`, which asks the built tool whether a key resolved so an unusable search is withheld, and `capabilities.py`, which reads keys to *report* -- which source supplied one, and whether one is there to reuse. Both ruling halves are delegated to the tools, whose files were already listed. Rollback is a revert. Nothing is written and no configuration is read differently. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A --------- Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
## Summary Prevent a failed MCP transport from cancelling the main agent turn during startup. Keep each MCP transport and handshake in one lifecycle so SDK task-group failures unwind into ordinary per-server connection errors. Raven can then continue with the remaining MCP servers and enter the model loop. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed - `uv run pytest tests/test_sandbox_unit.py -x -q` - 70 passed. - `uv run ruff check raven/agent/tools/mcp.py tests/test_sandbox_unit.py` - passed. - A real `raven agent` turn returned `RAVEN_FIXED_OK` after two configured MCP servers failed authentication. - Docs and screenshots are not needed because no user-facing contract changed. ## Risk - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes The change only alters failure isolation during MCP initialization. Roll back the single fix commit if transport lifecycle behavior regresses. ## Related Issues N/A --------- Co-authored-by: zhanghui <23442919+gloryfromca@users.noreply.github.com>
…verMind-AI#366) ## Summary PR EverMind-AI#360 reports every copy-on-select write as a permanent transcript line, so a reading session that drags through the transcript piles up "copied N characters" rows under the conversation. The confirmation splits by what the line is for: the unbounded terse repeats become a transient notice rendered above the input box -- one line that replaces itself on every new copy and clears after 3 seconds -- while the first copy of a session keeps its path caveat in the transcript. The caveat is the answer a user comes looking for after a paste comes up empty minutes later, and it cannot pile up because it is once per session by construction. The reporter still owns the per-session caveat tally and now returns `{ text, firstOfSession }` so the caller can route the caveat to the transcript and the repeats to the notice. A new `copyNoticeStore` holds the single notice; `showCopyNotice` keeps one dismiss timer, clearing any pending one before arming the next, so an earlier deadline can never clear a newer notice. `/copy` is deliberately untouched: an explicit command's answer still belongs in the transcript, and it cannot stack on its own. ## Type - [x] Fix ## Verification Run from `ui-tui/` at branch head, in a fresh worktree of `github/main`. Dependencies came from `npm ci` in `ui-tui/` and in the hermes package; the hermes package's own `node_modules` holds only esbuild, so the ink render tests run against one react copy (two copies break every hook call). ``` npx vitest run --no-file-parallelism Test Files 93 passed (93) Tests 1084 passed | 13 skipped (1097) npm run type-check clean, exit 0 npm run lint 22 problems (0 errors, 22 warnings) npx prettier --check src/app/copyNoticeStore.ts src/__tests__/copyNotice.test.tsx src/__tests__/clipboard.test.ts src/app/useMainApp.ts src/components/appLayout.tsx src/lib/clipboard.ts All matched files use Prettier code style! python3 -m scripts.check_commit_messages github/main..HEAD exit 0 ``` The 22 lint warnings are the baseline, not new: 22 before the branch and 22 after, none in a file this branch touches. The suite is run with `--no-file-parallelism` deliberately: the ink render tests fail under default worker parallelism at this suite size, and no CI job runs them. Suites that exercise the changed code directly: `src/__tests__/copyNotice.test.tsx` (7 cases), `src/__tests__/clipboard.test.ts` (30 cases, the 3 reporter cases updated to the new return shape). New tests were proved load-bearing rather than assumed: - Removing the notice render line in `appLayout.tsx` makes the render test fail (content and placement above the input box). - Removing the pending-timer clear in `showCopyNotice` makes the re-shown-notice test fail: the earlier 3s deadline then clears the newer notice. - The dismissed-notice case now shows a notice first, and removing `dismissCopyNotice`'s atom clear turns it red (review fix). - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed No doc change. `ui-tui/CONTEXT.md` has no clipboard or selection entry, and "copy notice" continues the existing "notice" vocabulary of `copyResultNotice` / `copyOnSelectNotice`, so nothing new is coined. ## Risk Behaviour change: the per-drag copy confirmation is now transient and above the input box instead of a permanent transcript line, and the first copy of a session lands one transcript line, exactly as on `main`. Reverting the branch restores the EverMind-AI#360 behaviour; no data or on-disk format is involved. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A --------- Co-authored-by: Claude (deepseek-v4-pro) <noreply@anthropic.com>
## Summary - Position Raven as both the runnable open-source harness and the broader research and ecosystem direction. - Define Raven as The Harness of Harnesses and explain the All-Domain Collaboration Network. - Highlight Trusted, Persistent, and Evolving behavior plus the evaluation, execution, verification, memory, and feedback loop in English and Chinese. - Surface the 22-task evaluation and quality-cost Pareto-frontier positioning before the published benchmark table. - Replace the old banner with a 129 KB GitHub-hosted Raven visual. ## Type - [ ] Fix - [ ] Feature - [x] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - `.venv/bin/pre-commit run --files README.md README.zh-CN.md` - passed - `env UV_CACHE_DIR=/private/tmp/raven-uv-cache make check-large-files` - passed - `git diff --check origin/main...HEAD` - passed - `git merge-tree --write-tree HEAD origin/main` - clean - No runtime tests were run because this change only updates documentation and externally hosted artwork. - [ ] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed ## Risk - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes The change affects repository messaging only. Rollback is a revert of the documentation commit and restoration of the previous banner URL. ## Related Issues N/A
guix4ever
force-pushed
the
fix/mcp_cancelled_error_escape
branch
from
August 27, 2026 03:01
f56d604 to
889e859
Compare
…d-AI#368) ## Summary - Clarify that the current public Raven release is the runnable self-improving Agent Harness. - Present The Harness of Harnesses as Raven's next-version direction and internal research prototype, not a capability of the current public release. - Align the English and Chinese banner notes, section headings, future-tense descriptions, and benchmark labels around that release boundary. ## Type - [ ] Fix - [ ] Feature - [x] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - [ ] Relevant tests pass locally - not run because this is a docs-only change. - [x] Relevant lint / type checks pass locally - `.venv/bin/pre-commit run --files README.md README.zh-CN.md` passed. - [x] User-facing docs or screenshots are updated when needed - `UV_CACHE_DIR=/private/tmp/raven-uv-cache make check-large-files` passed. - `git diff --check origin/main...HEAD` passed. ## Risk - [x] Security impact considered - no runtime code or dependencies changed. - [x] Backward compatibility considered - documentation only. - [x] Rollback path is clear for risky changes - revert the documentation commit. ## Related Issues N/A
## Summary - Replace the previous project link lists with the shared EverMind ecosystem tables from EverOS. - Keep the English and Chinese README sections aligned above their contributing sections. ## Type - [ ] Fix - [ ] Feature - [x] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification No runtime commands were run because this is a documentation-only change. Both README files were checked for one ecosystem section, exact shared table content, and valid placement. - [ ] Relevant tests pass locally - [ ] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed ## Risk - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A
guix4ever
force-pushed
the
fix/mcp_cancelled_error_escape
branch
from
August 28, 2026 02:58
889e859 to
991d583
Compare
…erMind-AI#370) ## Summary `raven trajectory` had eight id-oriented subcommands; using them meant copying ids out of `list` by hand. This PR changes the attempt data model and adds the human face, in four phases: 1. **Attempt definitions** (`attempts.json` sidecar): an attempt id equals the trace id unless a definition groups several traces under one minted id. Merge creates a definition (absorbing prior definitions and legacy groups as aliases, so verdicts/pins recorded under old ids stay visible); split deletes it, migrating pins down to members - naturally undoable, span logs stay append-only. Pin migration is linearized under a fixed lock order (attempts > pins); every failure and concurrency interleaving over-protects, never under-protects. Legacy logs (span-level `attempt.id`) stay addressable and mergeable, but cannot be split (their grouping lives in append-only spans). 2. **Write-side removal**: the span-level attempt mechanism (the begin/end/current_attempt trio and the `attempt.id` span attribute, shipped in v0.1.13) is deleted; `attempt.id` is now a reserved attribute key stripped from caller input, so `attempts.json` is the sole grouping source by mechanism. Readers keep resolving legacy logs; zero data migration. This is a breaking API change, declared in the BREAKING CHANGE footer below. 3. **CLI adaptation and reader defect tolerance**: `list` folds definition members into one row and surfaces verdicts through the alias set; new `merge`/`split` subcommands wrap the data layer thinly; `unpin` clears definition, aliases, members, and the literal id in one transaction. Two whole crash surfaces are closed along the way: Rich markup injection (a legal id may contain `[/red]`; every dynamic value is escaped), and JSON-legal but type-broken records (per-line UTF-8 decoding, required-field string validation, non-object attribute containers) now degrade per record/field instead of killing commands. 4. **Interactive browser**: a bare `raven trajectory` on a TTY opens a three-screen questionary flow (sessions -> attempts -> actions: save / report / minimize / verdict / pin|unpin / split, plus multi-select merge). Menus and action messages never show a session key, trace id, or attempt id - only artifact paths carry ids. Every action triggers a full rescan (report bundles and auto-pins before confirming, so even an aborted action changed state); data-layer errors surface as one fixed id-free message; aggregation reads one snapshot per refresh and deduplicates records into logical spans keyed by (traceId, spanId), so a root turn's checkpoint+final pair counts once. CONTEXT.md gains the Attempt Definition term and the final-state Attempt wording. ## Type - [ ] Fix - [x] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - `uv run pytest tests/test_cli_trajectory_browse.py tests/test_cli_trajectory_commands.py tests/test_trajectory_store.py tests/test_trajectory_bundle.py tests/test_tracing_api.py -q` -> 241 passed - `uv run pytest tests/ -q --ignore=tests/integration` -> 6838 passed; the 11 failures + 20 errors match main's pre-existing environment-specific set node-id for node-id (cron timezone cases, everos/config root-permission cases, viewer probe, theme), compared as sorted FAILED/ERROR sets against a baseline recorded before this branch - `uv run ruff check .` and `uv run ruff format --check .` -> clean - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed (CONTEXT.md terms) ## Risk - Bare `raven trajectory` changes behavior: it opens the browser on a TTY (previously the help page) and exits 2 with a hint when non-interactive. Scripts are unaffected: the subcommand check runs before the TTY gate. - New spans no longer carry `attempt.id`, and `trace.begin_attempt` / `trace.end_attempt` / `trace.current_attempt` are removed. These shipped in v0.1.13, so an integration calling them raises AttributeError right after upgrading - an explicit, immediately visible failure rather than a silent one. Migration: group attempts after recording with `merge_attempts()` (library) or `raven trajectory merge` (CLI); reader fallback keeps existing logs addressable with zero data migration. - `attempts.json` is the only new mutable state; deleting it reverts every attempt to single-turn semantics. Span logs remain append-only. - Rollback: revert the squash commit; no data migration either way. The one known crash window (process death between merge's two file writes) leaves a harmless over-protective pin, documented in the store module. - [x] Security impact considered (markup-injection and path-escape surfaces closed; ids treated as untrusted in every renderer) - [x] Backward compatibility considered (legacy span-attribute logs stay addressable, mergeable, and listable) - [x] Rollback path is clear for risky changes ## Related Issues EverMind-AI#362 (reference only: this PR removes the write-side attempt mechanism introduced there and shipped in v0.1.13). No issue is closed. BREAKING CHANGE: trace.begin_attempt, trace.end_attempt, and trace.current_attempt (shipped in v0.1.13) and the span-level attempt.id attribute are removed. Group attempts after recording instead: merge_attempts() in raven.trajectory, or `raven trajectory merge` in the CLI. Existing logs carrying a span-level attempt.id stay addressable through the reader fallback; no data migration is needed. --------- Co-authored-by: 江国庆 <guoqingjiang@deepglint.com> Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
…-AI#371) ## Summary Unit tests were leaking tracing spans into the real `~/.raven/traces` store. Tracing is on by default and `trace.span()` calls are embedded in library code (agent loop, subagents, TUI RPC), so any test exercising those paths emitted spans with fabricated session keys (`session-a`, `weixin:c`, `tui:default`, ...). Those spans then surfaced as phantom sessions in `raven trajectory`, while the TUI `/sessions` list (which reads the real session store) stayed clean. This PR adds an autouse fixture in `tests/conftest.py` that: - redirects `RAVEN_TRACING_DIR` to a per-test tmp directory, and - resets the lazy module-level `TraceStore` cache (`raven.tracing.spans._store`) around each test, since it pins the directory resolved at first emit. Key decisions: - Only the directory is redirected, not the enabled switch, so the suite keeps exercising the real emission path with zero behavior change; leaks land in tmp instead. - This centralizes the setenv-plus-cache-reset pattern that 8 tracing/trajectory test files already perform manually. Those tests keep working: their own `monkeypatch.setenv` runs after the fixture and wins, and they already reset the cache themselves. - Existing phantom sessions come from archived span logs written before this fix; the fixture only prevents new leaks. One archive file mixes a real session with test keys, so cleanup needs line-level filtering and is left out of scope. ## Type - [ ] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [x] Other Test-suite hygiene (`test` commit type). ## Verification - `uv run pytest tests/test_tracing_api.py tests/test_trajectory_store.py tests/test_trajectory_bundle.py tests/test_trajectory_replay.py tests/test_trajectory_redact.py tests/test_cli_tracing_commands.py tests/test_cli_trajectory_commands.py tests/test_cli_trajectory_browse.py -q`: 308 passed, 1 failed (`test_viewer_health_true_for_our_viewer`, fails identically on unmodified `main`, environment-specific) - `uv run pytest tests/test_agent_loop_run_emit.py tests/test_subagent_manager.py tests/test_tui_rpc_turn_send.py tests/test_deep_research_tool.py tests/test_agent_loop_approval.py -q`: 109 passed; the real `~/.raven/traces` log did not grow during the run - Positive check: running `tests/test_agent_loop_run_emit.py` with `--basetemp` shows the previously leaked session keys (`cli:c`, `tg:c`, `weixin:c`) now land in per-test tmp dirs - `uv run pytest tests/ --ignore=tests/integration -q`: 6837 passed, 11 failed, 20 errors; the same failure set reproduces on unmodified `main` (environment-specific: cron/tz, everos server, theme, config loader, viewer health), zero regressions from this change - `uv run ruff check tests/conftest.py` and `uv run ruff format --check tests/conftest.py`: both pass - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed Docs box left blank: test-only change, no user-facing docs affected. ## Risk - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes Test-only change; no production code touched. One known interaction: `RAVEN_TRACING_DIR` takes precedence over `RAVEN_HOME`, so a future test that fakes `RAVEN_HOME` and expects traces under it must also set `RAVEN_TRACING_DIR` itself. Rollback: revert the single commit. ## Related Issues N/A Co-authored-by: 江国庆 <guoqingjiang@deepglint.com> Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
guix4ever
force-pushed
the
fix/mcp_cancelled_error_escape
branch
from
August 30, 2026 16:48
991d583 to
6f84d44
Compare
…ify headroom (EverMind-AI#372) ## Summary Onboard model-default refreshes plus an EverOS startup fix: 1. Rerank default: Qwen/Qwen3-Reranker-4B has been retired from OpenRouter, so the onboard wizard's rerank example and EN/ZH recommendations now point to qwen/qwen3-reranker-8b, which OpenRouter still serves. 2. Stale defaults: the memory-LLM capability floor (gpt-4.1-mini), the multimodal recommendation (google/gemini-3-flash-preview), and the Azure OpenAI provider default (gpt-5.2-chat) all point at generations behind what OpenRouter/Azure serve today. They now read qwen/qwen3.8-flash (the EverOS client always sends temperature, which the GPT-5.x reasoning family rejects), google/gemini-3.7-flash, and gpt-5.6-sol. GPT-5.x Azure deployments force reasoning_effort="none" on tool calls, matching Microsoft's tool-calling requirement; configured effort still applies to non-tool calls. 3. Startup failure: an exhausted fs.inotify.max_user_instances makes the spawned EverOS server's watcher die at boot with "OSError: [Errno 24] inotify instance limit reached", which surfaces only as a cryptic exit buried in the dead-child log. ensure_everos_server now measures per-user inotify headroom (counting same-uid inotify instances via /proc/<pid>/fd links that read anon_inode:inotify against the kernel cap) before spawning: it raises the cap when the process is privileged, otherwise it fails fast with the exact sudo sysctl commands. A dead child whose log blames inotify gets the same hint appended to its error. ## Type - [x] Fix ## Verification - make coverage (full default suite, uv run --frozen --python 3.12 --all-extras pytest -q): 7119 passed, 2 failed, 37 skipped, 13 deselected. The 2 failures are pre-existing on main and environment-dependent (reproduced on main in a throwaway worktree): both expect a chmod-based write to be refused, which cannot happen when the suite runs as root (CAP_DAC_OVERRIDE): - tests/test_config_loader.py::test_migration_is_correct_even_when_the_file_cannot_be_written - tests/test_everos_server.py::TestAnUnwritableRootFailsAsAStartFailure::test_it_surfaces_as_runtime_error - make coverage-diff COVERAGE_BASE_REF=origin/main: 100.00% (57/57 executable changed lines), passed the 90.00% threshold - make coverage-ratchet: passed (line +2.11pp, branch +3.24pp over baseline) - uv run pytest tests/test_cli_onboard_commands.py: 251 passed - uv run pytest tests/test_azure_openai_provider.py: 9 passed - uv run ruff check (changed files): clean - uv run ruff format --check (changed files): clean - pre-commit commitlint hook (conventional commit message): passed on all commits - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally ## Risk - Security impact considered: the sysctl write runs only when the process already holds the privilege to write /proc/sys; otherwise the gate only returns the instructions. - Backward compatibility considered: the gate is a no-op when headroom exists or when procfs/sysctl cannot be read (non-Linux); the model-default changes are wizard text and one provider default constant, no config migration needed. - Rollback path is clear: revert the merge; the check is isolated to ensure_everos_server and three module-level helpers. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A
…d-AI#375) ## Summary The Feishu adapter's reconnect loop retried on a fixed 5s sleep with no backoff and no attempt cap. lark-oapi's ws Client.start() blocks while the connection lives (the SDK reconnects transient drops internally with its own 120s interval) and only returns by raising, almost always a ClientException such as bad credentials or connection-limit exceeded. The old loop therefore hammered Feishu's auth endpoint every 5s on permanent failures. Changes: - Retries now use exponential backoff capped at 300s (5s initial, factor 2): 5s, 10s, 20s, 40s, 80s, 160s, 300s. - ClientException is logged as a permanent error instead of a generic warning. - The backoff sleep runs in 1s slices so stop() can interrupt a long backoff promptly (no lingering thread). Three tests added covering ladder growth/cap, ClientException handling, and stop() interrupting the sleep. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed Commands and results: - `uv run pytest tests/test_channels_feishu.py -q` -> 38 passed (1 warning is pre-existing, from test_stop_blocks_zombie_inbound). - `uv run ruff check raven/channels/adapters/feishu/channel.py tests/test_channels_feishu.py` -> All checks passed. - `uv run ruff format raven/channels/adapters/feishu/channel.py tests/test_channels_feishu.py` -> 2 files reformatted (applied). - pre-commit hooks all passed, including the conventional commit message check. No user-facing docs affected: this is an internal retry-cadence change with no config or interface change. ## Risk - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes No security surface change: only retry timing is affected. No config, CLI, or API change; existing deployments behave the same apart from slower retries after repeated connection failures. Rollback: revert the single commit. ## Related Issues N/A --------- Co-authored-by: Claude (deepseek-v4-pro) <noreply@anthropic.com>
guix4ever
force-pushed
the
fix/mcp_cancelled_error_escape
branch
from
September 1, 2026 02:53
6f84d44 to
309eca9
Compare
## Summary Overhaul the interactive `raven trajectory` browser end to end, fixing every issue from real-terminal testing: - Navigation: every screen opens with a help area under its title (page purpose plus "[Key] function" hotkey hints, rendered in a new readable help color). Esc walks one level up (action -> attempt -> session); on the top-level session screen Esc stays put and quitting is Ctrl+C only, which prints a single exit notice. Esc inside a running action cancels just that action through a dedicated exception at the action boundary, so the back sentinel can never reach a conversion or the data layer; a cancelled report keeps its pre-confirm bundle-and-pin side effects (the existing declined-report contract). Back/Exit menu entries are gone; answered prompts erase themselves and navigation is echoed as breadcrumb lines with markup-escaped untrusted text. - Tables: the session list is TITLE | ATTEMPTS | LAST ACTIVITY and the attempt list is # | STARTED | TURNS | SPANS | VERDICT | PIN | MERGED | PREVIEW with green check cells. Time cells keep the year. The layout is budgeted per screen build from the live terminal width (row indent, a safety margin, and inter-column gaps are all part of the math); PREVIEW drops as a whole column when it no longer fits, and below the dynamic minimum width the tightest layout is kept while the renderer clips overlong lines at the terminal edge. Every dynamic cell passes a single-line sanitization gate before width math (verdict sidecars only guarantee a non-empty string), cells are measured and truncated by terminal display width (CJK is 2 cells), and fixed column widths always fit their headers. - Space preview: Space on an attempt row prints that attempt's per-turn input/output previews, collected in the same snapshot scan and sorted by a fully-stringified key so ordering never follows log source order (the table PREVIEW cell derives from the same sorted collection). Any key, arrow keys included, returns with the cursor kept on the row; the waiter passes the terminal's CPR reply through to the renderer instead of treating it as a keypress, and Ctrl+C keeps the browser-wide cancel. - Merge hotkey: with two or more attempts, m/M opens the multi-select merge (advertised in the help line; the old "Merge attempts" menu entry is gone). The checkbox screen carries its own help line for toggle, confirm, and cancel. A single attempt neither binds nor advertises the key. - Theme and feel: new success (green check) and help (readable hint text) colors in both palettes, all WCAG AA on their backgrounds; a lone ESC is flushed after 50ms instead of prompt_toolkit's 0.5s default, removing the laggy Esc feel. The data layer is untouched: changes are confined to raven/cli/trajectory_browse.py, raven/cli/_theme.py, and their tests. The id-taking subcommands (raven trajectory save/list/...) behave as before. ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - `COLORTERM=truecolor uv run pytest tests/test_cli_theme.py tests/test_cli_trajectory_browse.py tests/test_cli_trajectory_commands.py -q` -> 220 passed - `COLORTERM=truecolor uv run pytest tests/ --ignore=tests/integration -q` -> 6917 passed; the 10 failed + 20 errors are the pre-existing environment-dependent set, identical file-by-file on a clean main checkout (cron, everos server, config loader, tracing viewer) - `uv run ruff check` and `uv run ruff format --check` on the four touched files -> clean - PTY smoke on a CPR-answering pseudo terminal at 100 and 58 columns: full flow (preview, merge cancel, action screen, staged Esc backout, Ctrl+C quit) and the narrow-width degradation contract - New coverage includes real pipe-input key-binding tests (Esc on all four prompt kinds, Space/m/M hits, waiter key paths with CPR pass-through), table budget/clipping boundaries at the computed minimum width, and corrupt verdict/timestamp corpora - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed ## Risk - The interactive browser's look and key protocol change wholesale (that is the point of this PR): Esc no longer quits from the top level (Ctrl+C does), Back/Exit entries are gone, and lists render as tables. The machine-facing subcommands and the trajectory data layer are untouched. - Rollback: revert the squash commit; the browser returns to the previous interaction wholesale, no data migration involved. - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes ## Related Issues N/A --------- Co-authored-by: 江国庆 <guoqingjiang@deepglint.com> Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
guix4ever
force-pushed
the
fix/mcp_cancelled_error_escape
branch
from
September 6, 2026 10:51
309eca9 to
1956c6a
Compare
Member
This PR was auto-closed by a history rewrite, not by a maintainerOn 2026-09-12 the Your work is safe in your fork. Nothing in your repository was modified. To restore this PR, rebase onto the new git remote add upstream https://github.com/EverMind-AI/Raven.git # if you don't have it
git fetch upstream 'refs/tags/backup/pre-linearize-20260912:refs/tags/old-main'
git fetch upstream main
git rebase --onto upstream/main $(git merge-base <your-branch> old-main) <your-branch>
git push --force-with-leaseThen open a new PR and reference this one. Thank you for the contribution, and sorry for the disruption. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
asyncio.CancelledErrorleaked by an MCP transport or session during connection setup.Type
Verification
uv run pytest tests/test_sandbox_unit.py tests/test_tool_registry_timeout.py -x -q- 76 passed.uv run --extra dev ruff check raven/agent/tools/mcp.py tests/test_sandbox_unit.py- passed.uv run --extra dev ruff format --check raven/agent/tools/mcp.py tests/test_sandbox_unit.py- passed.git diff --check- passed.Relevant tests pass locally
Relevant lint / type checks pass locally
User-facing docs or screenshots are updated when needed
No user-facing documentation or screenshots are required because this change only corrects connection failure isolation.
Risk
External gateway or task cancellation still propagates when
Task.cancelling()reports an active cancellation request. Only a bare cancellation with no pending task cancellation is treated as an MCP server failure. Existing handling for ordinary exceptions and exception groups is unchanged. The change can be rolled back by reverting this commit.Related Issues
Closes #231